
到2016年10月,4年的iOS开发了,一边一边学习Java后台开发
能力说明:
了解变量作用域、Java类的结构,能够创建带main方法可执行的java应用,从命令行运行java程序;能够使用Java基本数据类型、运算符和控制结构、数组、循环结构书写和运行简单的Java程序。
暂时未有相关云产品技术能力~
阿里云技能认证
详细说明CSDN把我的博客关闭了!!! 进过一天的努力,CSDN竟然没一点回复,客服没找到,邮件发了N封,伤心之至,决定搬家到此,希望查封博客的事情,永远不要查封了!!! 你们可以封我的文章,可以不让别人看,但是不能不让我看吧?怎么说都没理由!!! 我开博客的目的,就是记录一些好的文章,研究一些东西,没有其他的用意 CSDN还不能在外网使用,有时候用着都非常蛋疼,那边查的东西好好的,回来对着博客看看,结果打不开。 彻底失望后,逃离那个封闭的博客,CSDN ,北北
Tomcat的配置 查看被占用的端口号 sudo lsof -i | grep LISTEN Tomcat是Java的web服务器,目前最新版是8.5.6,可以从这里下载到:http://tomcat.apache.org/download-80.cgi Tomcat并不区分Linux版和Mac版(但Windows版却是要区分的),下载下来就是一个tar.gz包,真正的绿色软件,解压,放到合适的位子去,就算完成安装了。一般来说,是放到/usr/local去,/usr目录就相当于Windows的“program files”目录嘛。 /usr/local/apache-tomcat-8.5.6 //为了防止误操作破坏系统,再用户状态下时没有权限操作系统重要文件的,所以先要取得root权限 sudo -s //给.sh加权 sudo chmod 755 *.sh //启动或者暂停Tomcat ./startup.sh ./shutdown.sh 我习惯性地不修改默认的目录名,依然叫“apache-tomcat-8.5.6”,但我会做一个软链接指向这个目录:(软链接) $cd /Library/Tomcat $ ln -s /usr/local/apache-tomcat-8.5.6 ApacheTomcat 这样就能轻易用/Users/用户名/ApacheTomcat去访问tomcat了。这样做还有一个好处,哪天Tomcat更新的新版本,我直接把Home指向新版本的目录即可,其它关于对Tomcat的路径引用的配置不用改,旧的版本可以继续保留用于测试,要换回去也很简单,改一下Home的指向即可。 启动Tomcat: $cd ApacheTomcat/bin $./startup.sh 立即用浏览器访问一下:http://localhost:8080/,你应该能看到: 配置管理员账号 $vim ApacheTomcat/conf/tomcat-users.xml <user username="admin" password=“qweasd" roles="manager-gui" /> 就设置好了一个叫admin的管理员,密码是qweasd。(这也能叫密码?)重启Tomcat生效。 需要在localhost后面加上8080会让你感到不爽,你想把这个去掉,使用默认端口号80,可以在这里配置: $vim ApacheTomcat/conf/server.xml 找到这一行: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> 把8080改为80即可。但!且慢,由于系统权限的问题,80端口不是随随便便谁都能开启的,你需要root权限来运行Tomcat,否则绑定端口就会失败。我建议是放弃,毕竟谁会用自己的Mac来做服务器呢?但我会把8080改为8079,这是因为后面用IntelliJ调试程序的时候,IntelliJ会启动新的Tomcat实例,大家都习惯性地使用8080这个端口,为了避免这个冲突,把默认的8080改一下是有必要的。 顺便提一下,在Mac下想知道哪些端口被占用了,可以用: $sudo lsof -i | grep LISTEN 在Linux下可以用netstat,但Mac下的netstat命令貌似跟Linux下的出入蛮大,不知道为什么会这样。 配置HTTPS 虽然没有绝对的安全,但大牛们说“不用SSL的安全都是‘假装安全’”。所以…… 从图中可以看出,我们要配置https其实就是要配置Connector,Connector在之前配置端口的地方已经接触过了,有印象吧?当然,我们还得准备些材料。那就是密钥,准备方法如下图: 如果你的个人目录(即“cd ~”转到的目录)已经有“.keystore”,那么还要提示你输入密钥库的口令,我这里秘钥库的口令是654321,tomcat这个密钥的口令也是654321。 这样一来,就在你的密钥库中创建了一个叫tomcat的密钥,其中只指明了“名字与姓氏”的信息为localhost,别的都可以留空。完之后你可以看看.keystore到底是个什么玩意儿: $od ~/.keystore 其实啥都看不出来,一堆加密的二进制码。 接着就是Tomcat的server.xml文件了: $vim /Library/Tomcat/Home/conf/server.xml 在<Service>节点中加一个<Connector>节点: <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystorePass="654321" /> 注意,一点都不能写错,包括字母大小写都要完全写正确方可。保存,重启服务器,打开:https://localhost:8443/ Safari浏览器提示你无法验证localhost身份,这是很显然的,你的证书是你自己造的,没有CA(证书颁发机构)的担保,所以浏览器默认是不信任你的,但你可以选择“继续”。 提示:https在实际生产环境中是非常有用的东西,但在开发环境中没什么用,我们只需要知道有这回事,这里先把这个配置拿掉。不拿掉的话后面运行程序的时候可能会出现一个8443端口被占用的错误提示。(尽管此错误其实也无关痛痒)。 添加web应用 有两种方法: 第一是直接把应用放在webapps目录下,Tomcat会自动解释; 另一种是在conf/Catalina/localhost下面放入一个xml文件,如放一个叫test.xml的文件,内容是: <Context docBase="/Users/guogangj/test" /> 然后在/Users/guogangj/test下创建一个index.html,随便写点内容: <!doctype html> <html> <head> <meta charset="UTF-8"> <title> test </title> </head> <body> <h1>test</h1> </body> </html> 然后访问:http://localhost:8079/test/index.html,就能看到test这几个大字了。 IntelliJ的安装 下载的安装包是个dmg,安装无压力,打开并拽入“应用程序”中即可。直接运行,根据提示进行一些默认的配置即可。 创建Java Web项目 说实在的,IntelliJ的项目创建方式不如其它IDE的直观,反正我一开始是没搞懂(其实搞懂也很简单),另外IntelliJ的不同版本之间是有差异的,网上找的一些资料并不准确,最好还是直接看官方文档,根据它的Tutorial走走,这次我看的官方文档是针对IntelliJ v12的,而现在我用的是v13,所幸的是差别并不大。 New Project,然后这样选: 那个Versions只能选3.1,貌似之前还能选3.0,这个版本其实是Servlet的版本,最新的版本是3.1,需要用Tomcat8来承载,如果你选择用Tomcat7来承载的话,会有一个warning说不认识这个版本,使用默认版本云云,忽略这个warning就是。 在下一步中指定项目名,SDK果断选择最新的1.8(Java8): 这样一来你的服务器的运行环境得部署为Java8,不过这个也没啥压力,Java8多了不少很有用的新特性,如果没有什么历史负担的话干嘛不用? Finish,我们现在来看看整个project的结构: .idea这是IntelliJ的相关东西,我们不用管,src目录用于放java源文件,web目录用于放web资源,WEB-INF是java web应用固定的存放配置及类库的目录,index.jsp是我们首页,HelloWorld.iml是IntelliJ的项目文件,打开工程就是打开它了,External Library是一些外部引用的库,展开看看好多。 现在我们来创建一个Servlet,Servlet是Java的服务器端小程序(其实也可以不小),右击src目录: 然后命名为SayHello: 展开,打开SayHello.java的时候却发现IntelliJ提示找不到符号: 这一定是因为某些包没引用。如何引用?一般都是设置CLASSPATH,告诉java如何去找它的包,而这里我们可以直接指定包的位置。 打开Project Struture设置对话框(快捷键为<Cmd>+<;>),如图: 点加号,选“Jars or directories…”,再找到Tomcat下的servlet-api.jar。 这样就可以了,我们把doPost删掉,用不到,再在doGet方法中写点东西输出,SayHello.java就变成这样: package com.mycompany; import java.io.IOException; import java.io.PrintWriter; /** * Created by guogangj on 14-5-13. */ public class SayHello extends javax.servlet.http.HttpServlet { protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Hello World!</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello World!</h1>"); out.println("</body>"); out.println("</html>"); } } java的代码写好了,配置文件也要加点东西,打开web.xml,加上一个“<servlet-mapping>”节点,改完后的web.xml变成这样: <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>SayHello</servlet-name> <servlet-class>com.mycompany.SayHello</servlet-class> </servlet> <servlet-mapping> <servlet-name>SayHello</servlet-name> <url-pattern>/sayhello</url-pattern> </servlet-mapping> </web-app> 编译(<Cmd>+<F9>),通过无压力。但,怎么运行? 运行Java Web项目 Java Web项目无法单独运行,它需要一个程序来承载(Host)它,这和微软体系的东西是很类似的,ASP.net程序需要IIS来承载对不?而现在我们很明显需要用Tomcat来承载这个Web程序。 首先我们要配置好Tomcat,<Cmd>+<,>打开IntelliJ的配置。 如上图那样配置好Tomcat。 然后打开Project的运行配置: 继续看图: 再看图,如此般设置: 这里它提示你有个问题,说缺乏artifacts配置,你可以顺着它的指引,fix一下即可。点OK。 这次可以跑了,<Shift>+<F10>。注意看IntelliJ的输出窗口里有什么提示信息,如果有,想想看是什么原因,我常常会碰到一些端口无法打开的问题,一般都是端口被占用了。 IntelliJ运行Java Web程序的时候会开启新的Tomcat实例,很可能会和之前运行的Tomcat实例发生冲突,解决冲突的最快的办法通常是直接把之前运行的Tomcat shutdown掉。 现在看看运行的成果吧:http://localhost:8080/sayhello 是不是看到“Hello World!”?这是用Java代码输出的“页面”,而不是静态页面。 打成war包 工程编译后生成的内容在/work/HelloWorld/out/production/下,我们要对其中的内容进行打包的话,可以这样: $tar cvf HelloWorld.war /work/HelloWorld/out/production IntelliJ当然也可以帮助你做这个动作,如图:在工程配置中选择artifact的类型,artifact不知道中文怎么翻译好,在很多游戏中,它都被翻译为“神器”,但这里可以简单把它理解为Java的发布包。 这样你就能在其中指定的Output directory中找到那个War包了,把War包直接丢到Tomcat的webapps目录下,Tomcat会自动加载它。 希望本文对读者起到抛砖引玉的作用。 http://www.cnblogs.com/guogangj/p/3725371.html
Java新手入门的30个基本概念 在我们学习Java的过程中,掌握其中的基本概念对我们的学习无论是J2SE,J2EE,J2ME都是很重要的,J2SE是Java的基础,所以有必要对其中的基本概念做以归纳,以便大家在以后的学习过程中更好的理解java的精髓,在此我总结了30条基本的概念。 Java概述: 目前Java主要应用于中间件的开发(middleware)---处理客户机于服务器之间的通信技术,早期的实践证明,Java不适合pc应用程序的开发,其发展逐渐变成在开发手持设备,互联网信息站,及车载计算机的开发. Java于其他语言所不同的是程序运行时提供了平台的独立性,称许可以在windows,solaris,linux其他操作系统上使用完全相同的代码. Java的语法与C++语法类似,C++/C程序员很容易掌握,而且Java是完全的彻底的面向对象的,其中提出了很好的GC(Garbage Collector)垃圾处理机制,防止内存溢出。 javaSe看成是java基础。J2EE看成是应用。 J2EE有两条路线: JSF+EJB3+JPA 与 轻量级的Struts2+Spring+Hibernate. 现在中小企业流行的是轻量级的SSH框架的web开发,大型企业大型项目才用到EJB3为核心的技术。 所以学习的话先从java基础、JSP、servlet、jdbc、SSH框架开始学,还要掌握MySQL或者Oracle数据库 Mac 上的 Java文章 http://blog.csdn.net/sike2008/article/details/42468729 () http://www.cnblogs.com/guogangj/p/3725371.html Java SE(Java Platform,Standard Edition)。Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为 Java Platform,Enterprise Edition(Java EE)提供基础。 Java EE(Java Platform,Enterprise Edition)。这个版本以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web 2.0 应用程序。 J2EE的实现之一Spring,其社区所支持的三十多个项目,涵盖了web、大数据、云计算、机器学习、移动终端、任务调度、插件体系、开发IDE、远程调用、消息中间件等,已经成了企业级开发中的事实标准。基于Http协议与Servlet写出来的 Java ME(Java Platform,Micro Edition)。这个版本以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。 Java的白皮书为我们提出了Java语言的11个关键特质。 (1)Easy:Java的语法比C++的相对简单,另一个方面就是Java能使软件在很小的机器上运行,基础解释其和类库的支持的大小约为40kb,增加基本的标准库和线程支持的内存需要增加125kb。 (2)分布式:Java带有很强大的TCP/IP协议族的例程库,Java应用程序能够通过URL来穿过网络来访问远程对象,由于servlet机制的出现,使Java编程非常的高效,现在许多的大的web server都支持servlet。 (3)OO:面向对象设计是把重点放在对象及对象的接口上的一个编程技术.其面向对象和C++有很多不同,在与多重继承的处理及Java的原类模型。 (4)健壮特质:Java采取了一个安全指针模型,能减小重写内存和数据崩溃的可能型。 (5)安全:Java用来设计网路和分布系统,这带来了新的安全问题,Java可以用来构建防病毒和防攻击的System.事实证明Java在防毒这一方面做的很优秀。 (6)中立体系结构:Java编译其生成体系结构中立的目标文件格式可以在很多处理器上执行,编译器产生的指令字节码(Javabytecode)实现此特性,此字节码可以在任何机器上解释执行。 (7)可移植:Java中对基本数据结构类型的大小和算法都有严格的规定所以可移植很好。 (8)多线程:Java处理多线程的过程很简单,Java把多线程实现交给底下操作系统或线程程序完成.所以多线程是Java作为服务器端开发语言的流行原因之一。 (9)Applet和servlet:能够在网页上执行的程序叫Applet,需要支持Java的浏览器很多,而applet支持动态的网页,这是很多其他语言所不能做到的。 基本概念: 1.OOP中唯一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构是怎样的,他只关系能否给你提供电就行 了,也就是只要知道can or not而不是how and why.所有的程序是由一定的属性和行为对象组成的,不同的对象的访问通过函数调用来完成,对象间所有的交流都是通过方法调用,通过对封装对象数据,很大 限度上提高复用率。 2.OOP中最重要的思想是类,类是模板是蓝图,从类中构造一个对象,即创建了这个类的一个实例(instance)。 3.封装:就是把数据和行为结合起在一个包中)并对对象使用者隐藏数据的实现过程,一个对象中的数据叫他的实例字段(instance field)。 4.通过扩展一个类来获得一个新类叫继承(inheritance),而所有的类都是由Object根超类扩展而得,根超类下文会做介绍。 5.对象的3个主要特点 behavior---说明这个对象能做什么. state---当对象施加方法时对象的反映. identity---与其他相似行为对象的区分标志. 每个对象有唯一的indentity 而这3者之间相互影响. 6.类之间的关系: use-a :依赖关系 has-a :聚合关系 is-a :继承关系--例:A类继承了B类,此时A类不仅有了B类的方法,还有其自己的方法.(个性存在于共性中) 7.构造对象使用构造器:构造器的提出,构造器是一种特殊的方法,构造对象并对其初始化。 例:Data类的构造器叫Data new Data()---构造一个新对象,且初始化当前时间. Data happyday=new Data()---把一个对象赋值给一个变量happyday,从而使该对象能够多次使用,此处要声明的使变量与对象变量二者是不同的.new返回的值是一个引用。 构造器特点:构造器可以有0个,一个或多个参数 构造器和类有相同的名字 一个类可以有多个构造器 构造器没有返回值 构造器总是和new运算符一起使用. 8.重载:当多个方法具有相同的名字而含有不同的参数时,便发生重载.编译器必须挑选出调用哪个方法。 9.包(package)Java允许把一个或多个类收集在一起成为一组,称作包,以便于组织任务,标准Java库分为许多包.java.lang java.util java,net等,包是分层次的所有的java包都在java和javax包层次内。 10.继承思想:允许在已经存在的类的基础上构建新的类,当你继承一个已经存在的类时,那么你就复用了这个类的方法和字段,同时你可以在新类中添加新的方法和字段。 11.扩展类:扩展类充分体现了is-a的继承关系. 形式为:class (子类) extends (基类)。 12.多态:在java中,对象变量是多态的.而java中不支持多重继承。 13.动态绑定:调用对象方法的机制。 (1)编译器检查对象声明的类型和方法名。 (2)编译器检查方法调用的参数类型。 (3)静态绑定:若方法类型为priavte static final 编译器会准确知道该调用哪个方法。 (4)当程序运行并且使用动态绑定来调用一个方法时,那么虚拟机必须调用x所指向的对象的实际类型相匹配的方法版本。 (5)动态绑定:是很重要的特性,它能使程序变得可扩展而不需要重编译已存代码。 14.final类:为防止他人从你的类上派生新类,此类是不可扩展的。 15.动态调用比静态调用花费的时间要长。 16.抽象类:规定一个或多个抽象方法的类本身必须定义为abstract。 例: public abstract string getDescripition 17.Java中的每一个类都是从Object类扩展而来的。 18.object类中的equal和toString方法。 equal用于测试一个对象是否同另一个对象相等。 toString返回一个代表该对象的字符串,几乎每一个类都会重载该方法,以便返回当前状态的正确表示. (toString 方法是一个很重要的方法) 19.通用编程:任何类类型的所有值都可以同object类性的变量来代替。 20.数组列表:ArrayList动态数组列表,是一个类库,定义在java.uitl包中,可自动调节数组的大小。 21.class类 object类中的getclass方法返回ckass类型的一个实例,程序启动时包含在main方法的类会被加载,虚拟机要加载他需要的所有类,每一个加载的类都要加载它需要的类。 22.class类为编写可动态操纵java代码的程序提供了强大的功能反射,这项功能为JavaBeans特别有用,使用反射Java能支持VB程序员习惯使用的工具。 能够分析类能力的程序叫反射器,Java中提供此功能的包叫Java.lang.reflect反射机制十分强大. 1.在运行时分析类的能力。 2.在运行时探察类的对象。 3.实现通用数组操纵代码。 4.提供方法对象。 而此机制主要针对是工具者而不是应用及程序。 反射机制中的最重要的部分是允许你检查类的结构.用到的API有: java.lang.reflect.Field 返回字段. java.reflect.Method 返回方法. java.lang.reflect.Constructor 返回参数. 方法指针:java没有方法指针,把一个方法的地址传给另一个方法,可以在后面调用它,而接口是更好的解决方案。 23.接口(Interface)说明类该做什么而不指定如何去做,一个类可以实现一个或多个interface。 24.接口不是一个类,而是对符合接口要求的类的一套规范。 若实现一个接口需要2个步骤: 1.声明类需要实现的指定接口。 2.提供接口中的所有方法的定义。 声明一个类实现一个接口需要使用implements 关键字 class actionB implements Comparable 其actionb需要提供CompareTo方法,接口不是类,不能用new实例化一个接口. 25.一个类只有一个超类,但一个类能实现多个接口。Java中的一个重要接口:Cloneable 26.接口和回调.编程一个常用的模式是回调模式,在这种模式中你可以指定当一个特定时间发生时回调对象上的方法。 例:ActionListener 接口监听. 类似的API有:java.swing.JOptionPane java.swing.Timer java.awt.Tookit 27.对象clone:clone方法是object一个保护方法,这意味着你的代码不能简单的调用它。 28.内部类:一个内部类的定义是定义在另一个内部的类。 原因是: 1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据。 2.对于同一个包中的其他类来说,内部类能够隐藏起来。 3.匿名内部类可以很方便的定义回调。 4.使用内部类可以非常方便的编写事件驱动程序。 29.代理类(proxy): 1.指定接口要求所有代码 2.object类定义的所有的方法(toString equals) 30.数据类型:Java是强调类型的语言,每个变量都必须先申明它都类型,java中总共有8个基本类型.4种是整型,2种是浮点型,一种是字符型,被用于Unicode编码中的字符,布尔型。
前言: iOS 9 发布之后,推出NetworkExtension, 它可给系统WiFi列表列表里边的WiFi设置密码 、标签(副标题)。 还可获取整个WiFi列表。 首先你得向苹果申请一个权限,人家允许你使用了,你再在工程里面配置一下,这样你才可以使用.苹果会给你发个问卷调查,根据你自己的情况填写.这儿谢谢我初中学霸,专业的英语翻译果然6. 1-1.框架申请链接:https://developer.apple.com/contact/network-extension 问卷调查表 根据自己的实际情况填写.里面有个产品介绍,最好找个英文好的... 1-2 调查表填写完成后,大概过了2 ,3小时,苹果会回复给你一封邮件,并且返回给你一个fllowup.(注意,这并不是代表你已经申请成功了,邮件里面只是确认你填写的问卷信息!) 并不是成功的邮件.png 1-3 接下来,就只能等了.逛苹果论坛,据说要等三星期,可是,我TM等了5星期是什么鬼.所以当超过三星期的时候我也没闲着,打电话 :4006701855 , 虽然得到的回复还是等... 而且,值得一提的是:有人遇见过这种情况,一直没有收到苹果拒绝或者同意的邮件.但是可以使用这个类.所以当超过3星期以后,我是每天都登录到开发者账号 配置描述文件,如果发现这儿多了一个选项.如下图,这也说明你申请成功了 配置描述文件 1-5 所以呢,打了两次电话,重发了5 ,6 次邮件之后,大概苹果也觉得不好意思了吧,终于通过了.此时你会收到这也一封邮件: 通过邮件.png 恭喜您,通过了.但是不得不说,这只成功了一小半.后面你可能遇见更痛苦的事情. 2下面就来来说更痛苦的事情. 2-1 配置工程 a .新建一个App ID. 新建 App ID b. 添加iCloud 和Wireless Accessory 添加iCloud 和Wireless Accessory.png c.新建iCloud Containers 新建iCloud Containers.png d.打开刚刚创建的App ID 发现这个是黄色的,下面就要编辑这个App ID 编辑刚刚创建的AppID.png e 17407914-2B9F-42D2-9E21-7EBF50DF0FEC.png f ABD85AAF-04C8-4FDD-B6E5-005D188980ED.png g 配置App ID完成 2DEACBF8-CB87-4CB0-A6B1-2E092DAD4369.png 2-2 配置描述文件 . 注意选择新建的App ID 注意这个值要加上.png 配置好这个之后,可以到苹果提供的检测环境检测一下配置文件的正确性 附上网址 :https://forums.developer.apple.com/message/75928#75928 这一步很重要:就是检测你的工程配置的描述文件和这个账号使用权限是否对等.后面有小伙伴遇到一个bug就是ruternType一直返回NO,获取不到wifi列表,然后通过上面验证发现,证书的权限和申请使用的权限不相同. 所以这儿有个建议,如果发现获取列表时返回值是NO,把测试证书删掉,重新创建.然后再走一遍上面的流程.(我遇到的BUG奇怪的很,对着英文文档走了好几遍,确定文件没什么问题,就是返回值一直都是NO,后来,在创建iCoud的时候,把那个id改成和App ID不一样,就是按照他下面的要求创建,就奇怪的好了.) 注意 :创建完描述文件别忘了安装到Xcode,直接下载,完了双击就ok了. 3.配置Xcode工程了; 3-1 配置plist文件 (允许后台运行) 通过xml添加 <key>UIBackgroundModes</key> <array> <string>network-authentication</string> </array> 通过xml方式添加.png 添加完了会有这 3-2 targets->Capabilities->iCloud 和Wireless-Accessory-Configuration ,打开并配置icould 打开Wireless-Accessory-Configuration.png 配置完上面两个你就会发现工程左边会多了一个.entitlements结尾的文件. 然后还要向这个文件里面添加一个BOOL值为YES的字段 com.apple.developer.networking.HotspotHelper 配置.entitlements文件.png 3-3 Tagarts-->Build Settings -> code Signing 配置Build Settings 3-4 上代码 (也是蛮辛苦的) Register a Hotspot Helper + (BOOL)registerWithOptions:(NSDictionary*)options queue:(dispatch_queue_t)queue handler:(NEHotspotHelperHandler)handler @param options kNEHotspotHelperOptionDisplayName :WIFI的注释tag字符串// 此处设置的内容会在WiFi列表中每个WiFi下边展示出来 @param queue dispatch_queue_t 用来调用handle的block @param handler NEHotspotHelperHandler block 用于执行处理 helper commands. @return 注册成功YES, 否则NO. @discussion 一旦这个API调用成功,应用程序有资格在后台启动,并参与各种热点相关的功能。 当应用程序启动此方法应该调用一次。再次调用它不会产生影响,并返回NO。 这个方法是主要的. + (BOOL)logoff:(NEHotspotNetwork *)network @param network 对应当前关联的WiFi网络NEHotspotNetwork @return 注销命令已成功进入队列YES, 否则NO. @discussion 调用此方法使kNEHotspotHelperCommandTypeLogoff型的NEHotspotHelperCommand向应用程序发出的“handler”模块 网络参数必须符合当前关联的WiFi网络,即它必须来自对NEHotspotHelperCommand网络属性或方法supportedInterfaces + (NSArray *)supportedNetworkInterfaces @return 如果没有网络接口被管理,返回nil。否则,返回NEHotspotNetwork对象数组。 @discussion 每个网络接口由NEHotspotNetwork对象表示。当前返回的数组包含一个NEHotspotNetwork对象代表Wi-Fi接口。 这种方法的主要目的是当没有得到一个命令来处理它时,让一个热点助手偶尔提供在UI里其准确的状态。 此方法加上NEHotspotNetwork的isChosenHelper方法允许应用程序知道它是否是当前处理的网络。 +(void)getWifiList{ NSMutableDictionary* options = [[NSMutableDictionary alloc] init]; [options setObject:@"
图像:1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存。可对图片进行缩放等操作。 下载:https://github.com/mwaterfall/MWPhotoBrowser目前比较活跃的社区仍旧是Github,除此以外也有一些不错的库散落在Google Code、SourceForge等地方。由于Github社区太过主流,这里主要介绍一下Github里面流行的iOS库。 首先整理了一份Github上排名靠前的iOS库(大概600个repos) 除了逛一下每日/每月流行之外,也可以到这里来看一下整个iOS Repos的排名。 下面是一些比较流行的第三方库: HTTP相比较之下,AFNetworking是目前最优秀的一个了:轻量、易用、使用者多、开发者有在积极维护。在AFN出现之前,这个角色是由ASIHTTPRequest扮演的,只是到现在年久失修了。关于AFN和ASI的对比,这里有一篇不错的文章http://www.infoq.com/cn/articles/afn_vs_asi。除此之外,MKNetworkKit和RestKit也有一定的使用者。 SocketCocoaAsyncSocket无疑是目前封装得最完善的Socket库了:支持异步TCP/UDP,支持GCD,Objective-C接口封装。。目前没有发现可以与之相比的同类产品。。 JSONJSONKit算是第三方中最优秀的一个了:性能很高,文件少。在JSONKit之前,SBJson非常非常流行,但是SBJson性能够差,只是由于历史原因仍然存在在某些工程里面。如果工程只需要支持iOS5以上的系统,那就可以放弃那些第三方Json库了,直接用系统提供的NSJSONSerialization,性能比第三方的好,又是官方API。。 XMPP现在做个实时聊天,XMPP协议算是很成熟的方案了。XMPPFramework一个很不错的选择,可以直接和OpenFire服务器打交道。项目不大人手不多的话,可以看看这个。 基础工具类SSToolkit算是一个不错的工具包,提供各种比如编码、加密、字符串处理等等东西,还提供了一些不错的自定义控件,并且文档非常齐全。 框架过去有很多人再用three20,这个东西太大太重,文档又少,到头来连Facebook都停止维护了。作为替代品nimbus现在流行了开来,关键在于它文档齐全。国内有个MVC框架叫BeeFramework,号称是顶级框架并且功能超过nimbus,有兴趣的可以看一下。 ReactiveCocoa把响应式编程这种上流的东西带了过来,值得试一试。。 数据存储还是挺多人(比如我)喜欢直接跟SQLite打交道的,这方面fmdb封装的很不错。如果用CoreData来做存储的,可以用一下MagicalRecord。 图像处理GPUImage无疑是这方面的集大成者了。用OpenGL ES2.0来实时处理图片和视频流,性能和功能都是顶尖的。 开发和调试工具PonyDebugger看上去是一个不错的调试工具,可以在电脑浏览器上远程调试iOS程序、查看试图层次、网络等等。CocoaLumberjack是个Log工具,号称是可以提供企业级Log,使用者也挺多。 为了了解一下目前第三方库的普及程度,下面列举一些知名App对第三方库的依赖。 网易新闻AppleReachabilityASIHTTPRequestEGOTableViewPullRefreshGTMNSString+HTMLMGTemplateEngineMPOAuthRegexKitLiteSDWebImageSSZipArchivewax GaragebandMurmurHashlibpngzlibSBJson (json-framework) iWork三套件MOKitBoost C++ LibraryprotobufOpenGL MathematicsSQLitecephes math library PinterestAFNetworkingAFHttpClientLoggerFacebook SDKiRateMAKVONotificationCenterSDWebImageSFHFKeychainUtilsSSPullToRefreshSVProgressHUDTTTAttributedLabelTTTLocalizedPluralStringUIAlertView-Blocks 多看阅读fmdbASIHTTPRequestFreeTypeJSONKitObjective-ZipSkia (Google)MBProgressHUD 淘宝MAZeroingWeakRefMBProgressHUDABContactHelperASIHTTPRequestCocoaLumberjackEGOTableViewPullRefreshfmdbGTMBase64JSONKitSBJson (json-framework)RTLabelSDWebImageSVPullToRefreshthree20ziparchive 微信cocos2dEGOTableViewPullRefreshFacebook iOS SDKJSONKitSBJsonziparchive QQASIHTTPRequestFMDBCocoaAsyncSocketJSONKitMBProgressHUDOpenUDIDSBJsonSVPullToRefresh 百度地图AFNetworkingGTMBase64JSONKitMBProgressHUDRNCachingURLProtocolSDWebImage 微博ABContactHelperAFNetworkingASIHTTPRequestDACircularProgressViewDDProgressViewDTFoundationfmdbJSONKitSBJsonMBProgressHUDMTStatusBarOverlayOpenUDIDSFHFKeychainUtils 人人cocoaasyncsocketZipArchiveMBProgressHUDJSONKitGTMBase64MKNetworkKitHPGrowingTextViewzxing 可以看到,这些大型的App的依赖都很混乱,所以稍微解释一下。这些大公司都有一个iOS团队来协同开发,团队成员的水平也参差不齐。有时由于历史原因,例如某个App的某个组件依赖了ASIHttpRequest,但之后的新人改用了AFNetworking,就造成上面这种比较混乱的库依赖关系。这就造成难以维护、代码冗余等问题了。所以,引入一个第三方库一定要慎重考虑,如果可能,尽量自己开发和实现相应的功能,第三方库尽量只作为参考。 小团队或者个人开发者可以不必过多考虑,开发速度优先。-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------2014年8月7号新增系统基础库Category/Util sstoolkit 一套Category类型的库,附带很多自定义控件 功能不错~ BlocksKit 将Block风格带入UIKit和Founcation cocoa-helpers 一些Cocoa的扩展 2年前的工程 CoconutKit 一系列扩展和一些自定组件 STUtils 一系列扩展包 PSFoundation 一系列扩展 和功能增强 ConciseKit 一系列宏定义 挺有意思 DTFoundation 又一系列扩展。。。 BBBootstrap 一些常见功能的扩展库 cooliris-toolkit cooliris出品的基础工具库,从GoogleCode clone过来 BaseKit 一套基础工具库 NSObject-Utility-Categories 如题 NSArray-Utilities 如题 aqtoolkit 一些常用的东西 DLIntrospection 一些NSObject的扩展 NSDate-Extensions 如题日期 ALActionBlocks 在UIControl上添加的Block事件 jrswizzle 主要是swizzling moriarty 一些还算有用的功能 QSUtilities 一些基础库 (Instagram有用到) libextobjc 一堆oc的扩展 PrettyTimestamp 友好时间的扩展 ios-jail-break-detector 检测是否越狱 有上线 crackify 检测App是否被破解 NSBundle-OBCodeSigningInfo 检查当前签名、Sandbox状态 MacOSX UIView-AnimatedProperty 为UIView添加动画效果的property,有用到swizzle Block-KVO Block风格的KVO~ MTDates 一些Data的category CocoaSecurity 常见的哈希、加密解密算法 sskeychain 访问keychain 支持iOS Mac ABContactHelper 访问联系人 UISS 用JSON的风格设置UIKit样式 Underscore.m 仿JS的underscore.js来写的,用于访问和处理数据 TICoreDataSync 在不同设备间对CoreData进行同步 (iPhone iPad Mac) SoloComponents-iOS 几个轻量的组件,没什么用,2年没更新 MAObjCRuntime 将运行时包装成ObjC Objective-C-Generics 通过宏定义勉强实现的范型 TBMacros 一些常用的宏定义 NSString-Ruby 为Ruby程序员带来的字符串相关功能 ObjectiveRecord CoreData包装的活动记录 有RoR风格的API FastImageCache 滑动时快速显示图片的库,Path开发 基础功能 mediaextract 纯C的一些常用媒体文件的解析读取 objection 一个估计是java ee写多了的人搞的。。注解依赖注入xx工厂之类的 Typhoon IoC容器啊~~ HTKit 一票乱七八糟的东西 StringScore 模糊匹配字符串 查找某两个字符串的相似程度 RegexKitLite 正则表达式库 (从SF.net mirror过来) FormatterKit 一个NSString的格式化工具 TMCache 一个内存Cache EGOCache EGOImage里面的 Cache功能 HJCache 一个缓存网络内容等的Cache RNCryptor 加密解密相关 貌似更上流, note:去看一下他博客 ssziparchive zip压缩 zip解压~ ZipKit 一个ZipKit 从bitbucket Mirror过来 ZipKit 如同ZipKit 改为ARC了 zipzap Zip文件处理 底层为libz MTMigration 版本升时执行一次 MTControl jQuery风格touch事件绑定 storage 快速、线程安全的I/O存储操作 CHCSVParser CVS文件解析 YLMoment 日期的解析,格式化等,支持多语言 CMUnistrokeGestureRecognizer 各种奇怪的手势检测 EKAlgorithms.git 常见算法的ObjC实现,包括排序、查找、数组、字符串、常用数据结构.. 框架/封装 three20 一个曾经用得广泛的庞大的iOS开发框架 已不再维护 nimbus three20替代品 又一个庞大的框架 BeeFramework 一个基于MVC的框架, 比较大.. OmniGroup 一套很庞大的库(OmniGroup) 支持Mac和iOS 更新迅速 ReactiveCocoa 一套响应式编程的框架(Functional Reactive Programming) 支持Mac WebViewJavascriptBridge WebView和Cocoa之间的事件传递 OCUDL 自定义literals 有点意思~~ Kiwi 一个Behavior驱动编程框架? (BDD)? Mantle 一个Model层, 替代CoreData 可以生成Model对象~ Github官方出品.. MagicalRecord CoreData的包装 简化代码 fmdb SQLite的封装 KZPropertyMapper 一个Model的封装,类JSON BlockInjection 为某个方法注入一个Block 类似AOP NSObject-ObjectMap JSON和XML包装到Object Ejecta 通过OpenGL OpenAL来实现 WebCanvas JS等,很庞大~ FCModel 为SQL程序员包装一下CoreData 功能/封装 iOS-System-Services 获取各种设备信息 包括硬件、网络、内存、进程等等 MKStoreKit iAP用的库 CargoBay 一个包装iAP StoreKit的库 RMStore 很轻量的iAP包装 QuincyKit 处理crach信息的库 只有上架应用才能用 支持Mac OpenUDID 系统UDID替代方案 UIDevice-with-UniqueIdentifier-for-iOS-5 另一个UUID解决方案 但iOS7不能用了 uidevice-extension 一系列UIDevice的扩展 有PrivateAPI 最新的工程在cookbook-code里 InAppSettingsKit 包装了设置、URLScheme、Mail、Icon等东西 DTWebArchive 允许访问剪贴板中的WebArchive格式富文本 routable-ios inApp的URL定义 在App内打开自定义URL (解耦) JLRoutes 复杂的URL Scheme解析和路由 Emoji 访问Emoji图片的功能 PDKeychainBindingsController 以类似UserDefault的方式访问Keychain CoreTextWrapper 一个CoreText库的包装 Slash 对AttributeString的封装 iHasApp 检测某个App是否安装,集成了常见的URL Scheme 网络基础 AFNetworking 很棒的HTTP网络通信库 asi-http-request 一个HTTP库 同样很知名 性能好,稳定 但已不再维护 MKNetworkKit 一个HTTP库 支持Mac 同样也不错 STHTTPRequest 一个非常简单的http库,包装了NSURLConnection RestKit 一个HTTP库 用来与RESTful的服务进行交互 AFIncrementalStore 用CoreData和AFNetworking 将HTTP数据持久化 CocoaAsyncSocket TCP/UDP包装库 支持Mac 很棒 Reachability 判断设备网络情况 和苹果的很像 用ARC、GCD SDReachability 判断设备网络情况 和苹果的很像,更友好? socket.IO-objc 支持HTTP 长轮询 socket.io DTBonjour Bonjour和Wifi XMPPFramework XMPP客户端的库~ 支持Mac CocoaHTTPServer 一个轻量的HTTP Server 支持Mac SocketRocket WebSocket客户端~ MailCore IMAP SMTP邮件协议 JSON JSONKit 性能非常好 (只比Apple原生的差一点) json-framework SBJson 曾经用得很广泛的JSON 性能差 TouchJSON 又一个JSON库 yajl-objc 一个JSON库 可以支持流解析 支持Mac JSONModel 一个构建JSON Model的工具和库 XML/Html MWFeedParser Feed/Atom解析 RSSKit RSS Atom解析 ElementParser 提供Html和XML的解析 hpple 提供Html/XML解析 Objective-C-HMTL-Parser 一个很简单的HTML解析 包装了下libxml RaptureXML 一个简单的XML解析器 包装了libxml KissXML 解析XML的库 包装了libxml TBXML 快速解析xml的库 xmldocument 又一个解析XML的库 XMLDictionary 蛮好用的一个XML转NSDictionary的类~ XML-to-NSDictionary XML转NSDictionary MMMarkdown 一个渲染Markdown到HTML的小引擎 Ashton 在NSAttrbuteString和Html之间转换 XMLParser XML解析成json~ 功能性 EGOImageLoading 下载使用网络图片的库 SDWebImage 异步加载网络图片 (UIImage Category) TCBlobDownload 下载大文件/ 断点续传 GroundControl 一个通过远程plist改变配置的库 appirater 提醒用户稍后到AppStore给你评分的功能 iRate 类似appirater 提醒用户稍后到AppStore评分 支持Mac ShareKit 分享到国外的一些社交网站 的功能 用的的人挺多 Harpy 新版本更新提醒 iVersion 新版本更新提醒 功能提示 DataKit 以类似CoreData的Model 来获取网络资源 SDURLCache URL缓存 objective-git libgit的包装 挺庞大 nsrails RoR风格的网络交互API SDK facebook-ios-sdk Facebook的SDK MGTwitterEngine 一套Twtter库 Twitter-OAuth-iPhone Twitter的OAuth功能 Foursquare-API-v2 Foursquare的库 objectiveflickr Flicker API octokit.objc Github的API ParcelKit Dropbox封装CoreData cocoalibspotify spotify官方SDK GDFileManagerKit 访问 dropbox GDrive之类的云存储 音频 iOSSystemSoundsLibrary 列出和播放iOS系统声音~ novocaine 一个声音库 录音放音 页面上有CoreAudio的教程链接 TheAmazingAudioEngine 一个声音库 包装了CoreAudio AudioStreamer 音频流播放库 支持Mac SimpleAudioEngine 一个简单的播放声音的库 DOUAudioStreamer 豆瓣的音频流播放 audiograph 一个AU的教程 & 示例工程~ NVDSP 一个iOS上的DSP处理 Blip-Synth 一个Blip声音合成器 PGMidi 一个Midi库 sc_listener 检测系统声音等级的库 ObjectAL-for-iPhone 一个OpenAL的包装库 比较好用 SoundBankPlayer 一个仿SoundFout的库 但更简单 Finch 对底层的OpenAL包装 SoundManager 很简单的Sound播放管理 OrigamiEngine 音乐播放引擎 支持 flac, cue, mp3, m4a, m3u libpd 一个用PD来创造音乐的库? 纯C pd-for-ios 同libpd RCTMidiLib 一个Pad连接Midi设备通信的包装 octave 一个免费的音效库 包括48个UI音效 RBDMuteSwitch 在iOS5上检测是否静音 sfArkLib 转换sfArk和sf2的库 很简单 sfArkXTm 简单解压sfArk的一个cpp unsfark 和上面sfArkXTm一样,是老一点的纯C版本 MuseScore 一个跨平台的音乐软件~ not iOS OpenSFZ 一个SF2的处理库(Github上有详细说明) An Open SFZ player & SF2 based on SFZero alsaplayer 一个Linux下PCM库 有sf2 midi等处理过程 游戏 cocos2d-iphone 2D游戏引擎 (SpriteKit后来居上了..) cocos2d-iphone-extensions cocos2d的扩展 smooth-drawing cocos2d中画出平滑的曲线 tiny-wings 仿TinyWings的Demo 用Cocos2D Tiny-Wings-Remake-on-Android 如题. tweejump 是小鸟就跳100层 Game wizardwar 一个很完整的2D小游戏,支持对战,已在AppStore上架 Sparrow-Framework 开源游戏引擎 2D 1.0版 Sparrow-Framework 开源游戏引擎 2D 2.0版 图像GPU OpenGL GPUImage 基于GPU的实时图像视频处理 XBImageFilters 一个GPU实时处理的库 类似GPUImage CeedGL 一个OpenGL的包装 用起来更方便 rend-ios 一个OpenGL的轻量包装 接近UIKit GLView 在UIView的包装 方便使用OpenGL sift-gpu-iphone 一个包装GL用的,不知道干毛的 ShaderManager 如题 一个Manager 管理创建Shader GLImageProcessing 演示如何用GPU处理图片 FRD3DBarChart 用OpenGL绘制的3D柱形图 EarthView 显示3D地形图 用OpenGL CoreAR 一个挺有意思的AR VRToolKit 一个虚拟现实的库 图像处理 NYXImagesKit 一套图像处理的库 加在UIImage上 ios-image-filters UIImage扩展 类似PS的功能 用CoreImage vImageCategory UIImage扩展 简单的一些处理 MGImageUtilities 几个简单的图片处理 UIImage UIImageAdjust 一些UIImage的扩展 如亮度 模糊等 CKImageAdditions 一些UIImage的不错功能~ UIImage-BlurredFrame 仅模糊图片中的一块区域~ LBBlurredImage UIImage扩展 可以模糊 不知道性能如何 FilterKit 为拍照添加滤镜 基于GPUImage TCam 用CIFIlter实现的类似Instagram的类 CLImageEditor 一个功能齐全的图像处理(滤镜/曲线/裁减/旋转/等等~) PKCoreTechniques CA和CG的特性演示 UIImage-Categories UIImage的一些扩展 uiimage-dsp UIImage模糊的一些方法 UIImage-Sprite-Additions UIImage取Sprite~ iOS-Scratch-n-See 类似结冰玻璃用手画开的效果~ OBGradientView 一个渐变色的CALayer 比系统的好用些 ANImageBitmapRep 一些Bitmap处理的方法 KGNoise 产生带噪音的Image 支持Mac KGNoiseColorTester 上面KGNoise的演示 SQRiskCursor 一个用CA和UIControl自定义控件的例子 AmazeKit 据说是为PNG加速 或自己画 主要为控件提升性能的 UIImage-PDF 如题 UIImage来显示PDF JMNoise 给UIView添加Noise噪音的 SWSnapshotStackView 图片加上Stack外框 RMShapedImageView 一个UIImageView的子类 能忽略透明地方的点击 MOOMaskedIconView 一个通过Mask显示Icon的库 只要一个Mask 可以生成很多风格的Icon 类似Tabbar FTAssetRenderer 运行时的图像Mask生成? wolfpack 图像处理库CI CG DSGraphicsKit 支持图片常见处理 视图控制3D等 uiimage-from-animated-gif 显示gif cam 一个AVFoundation的封装、照相等功能 Filtrr 几个Filter 速度也不行 ZXingObjC ZXing 二维码/条形码处理 颜色 uicolor-utilities UIColor扩展和色板 color 一系列UIColor扩展 ColorUtils 一个UIColor扩展 ColorConverter RGB和HSL转换 InfColorPicker 一个色彩选择器 RSColorPicker 一个不错的色彩选择器 有放大镜 colorpicker 一个很强大的ColorPicker 包装了挺多东西 ColorPicker 功能很棒的一个取色 ILColorPicker 一个还算标准的ColorPicker NPColorPicker 一个HSV三角取色器 KZColorPicker 又一个HSV圆盘取色器 Color-Picker-for-iOS 一个简单的单色Picker 有一个滑块 LEColorPicker 一个ColorPicker可以取到图片的主色调等 UIColor-converter 一些UIColor的工具~~ SUColor 提供2K多的日本色 Colours-for-iOS 提供不少常用色 和几个方法 ColorArt 类似iTunes的 取一幅图画的主要背景颜色等 (这是个Mac功能) iOS7Colors iOS7的几种常用颜色 绘图库 iOSPlot 画饼图/折线图的库 用CA PNChart 扁平风格的折线图/柱形图 TEAChart 扁平风格的饼图/柱形图 ios-linechart 简单好用的饼图/折线图 JYRadarChart 雷达图(螂蛛网图), 风格多样 Core-Animation-Pie-Chart 用CA画饼图 示例 MSSimpleGauge 扇形仪表盘 FBDigitalFont LED液晶效果荧光字体,用CG实现 FBGlowLabel LED液晶效果荧光字体,UILabel ADGraphView 一个CG绘画库 股票曲线演示 SVGKit 一个用CA来呈现SVG图片的库 Smooth-Line-View 用QuartZ画出平滑的曲线 Smooth-Line-View 用上面的库搭建的比较完整的画板功能 有取色器等 SignatureDemo 一个QuartZ 触摸画平滑曲线的Demo~~~ DynamicGraphView 动态折线图 XYPieChart 饼图~有动画 Animated-Paths 动画画出文字和图形的轮廓~ 动画/效果CA CA360 一套完整的CA动画Demo Core-Animation-Fun-House 一套CA动画展示Demo Core-Animation-Demos 一套CA动画Demo EnterTheMatrix CA动画演示Demo ftutils 一套CA的Util库 有一些常用动画 Dazzle 用CA实现的各种有趣的粒子效果 AGGeometryKit CA和3D效果等 RBBAnimation 基于block的CA动画 动画曲线 NSBKeyframeAnimation 那些jQuery带的动画速率曲线 CAAnimation-EasingEquations 一些CA没有的动画曲线 UIView-EasingFunctions UIView的动画曲线~~ PhysicsAnimation 物理动画,用Chipmunk实现的重力效果等 DPMeterView 有重力感应的2D图形动画(例如瓶子的水) 貌似挺有意思~~ Parallax iOS7的景深背景动画效果 一般.. MTAnimation 25种动画曲线~~~ SKBounceAnimation 弹跳的动画曲线 常见动画 BCGenieEffect 果冻吸入的效果 和Mac里最小化的效果一样~~很棒~ iCarousel 一套类似CoverFlow的空间,非常棒,可选多样式,可用于Mac OpenFlow 类似CoverFlow的效果 挺老的工程 支持Mac PaperFold-for-iOS 一套类似报纸展开的仿3D效果 用CA实现 MPFoldTransition 那个能左右上下Cube/Paper翻页的效果 挺棒 iOS-Flip-Transform 一个Flip翻页效果 支持上下左右翻页 会变暗 AFKPageFlipper 一个Flip翻页效果 可能性能不够好 也不会变暗 DoorwayTransition 一个OpenDoor的动画 Flipboard-3D-Transform-Effect-Example 如题Flip效果 没测试 PaperFoldMenuController 一个Map折纸动画效果 一般 XYOrigami Map折纸动画~ PaperStack 用OpenGL实现的类似iBook的效果 但是一般 GCRetractableSectionController 让TableView能按Group展开合起 HMGLTransitions 一个OpenGL的视图切换 跑不起来? 很久没更新 EPGLTransitionView 一个OpenGL的视图 包括iBook效果 折纸翻页效果 散落效果 leaves 一套有些类似iBook的效果但只有横向翻页 被用在看书的地方 GC3DFlipTransitionStyleSegue 用GL实现的iBook 一般 KNSemiModalViewController 一个推出ModalView的动画 后面的会缩小上移 letterpressexplosion UIView的撕碎爆炸效果 (CA实现) FancySegue 几个GL实现的动画 虽然不怎么好 CoreImageTransition 用CI实现的一些动画过渡 过雾~ LTransitionImageView 一个Image的过渡效果 STScratchView 刮奖的效果 XBPageCurl GL实现的翻页效果~ ADTransitionController 很多有趣的导航视图切换动画 VCTransitionsLibrary iOS7下,一些自定义转场动画~ 挺棒 JazzHands IFTTT简介的动画框架,是个关键帧动画框架哟~ 控件HUD MBProgressHUD 一个HUD 用得很广泛 无阴影 全屏模态 MBProgressHUD 和MBHUD一样 但有更新 添加了BLock iOS6 ATMHud 很棒的HUD 有阴影和动画~ SVProgressHUD 一个HUD 样式类似MB_HUD 有一些进度等 HTProgressHUD 又一个HUD ETActivityIndicatorView Windows Phone 7风格风火轮 Slider JMSlider 一个很小清新的Slider 用CA画的 iOS-Custom-Controls 按住可以出Pop的UISlider MTZTiltReflectionSlider 一个模仿iOS6 Music的Slider ASRangeSlider 一个两段式的Slider DCFineTuneSlider 可以拖动外部 和显示左右箭头的Slider ARAnnotatedSlider KVO教学 一个在Slider上面显示小Poper的~ LARSBar 显示声音dB的Slider EDStarRating 打星评级 Slider控件 OBSlider 模拟iPhone音乐进度的表现 TLTiltSlider 仿iOS6 音量Slider,有金属光泽动画 RETrimControl 类似iOS6录像裁减的控制条 Progress KOAProgressBar 自定义ProgressBar DDProgressView 平面风格的Progress进度条 WNProgressView 一些有趣风格的ProgressView YLProgressBar 一些不错样式的ProgessBar DACircularProgress 圆环形进度条 MRProgress 圆环进度条,iOS7风格,iOS7 Only MDRadialProgress 圆环进度条,多种风格 iOS6+ 侧滑 ViewDeck 左右侧滑控件 MSDynamicsDrawerViewController iOS7 动态弹性和景深的侧滑~ ECSlidingViewController 一个很简单的侧滑控件 JASidePanels 一套不错样式的侧滑控件 PSStackedView 一套侧滑且带Stack的控件,类似Twitter iPad PKRevealController 一个侧滑控件 JTRevealSidebarDemo 一个类似FB侧滑的Demo PPRevealSideViewController 又一个类似FB的侧滑控件 CLCascade 类似Twitter iPad版的侧滑控件 SlideViewController 侧滑菜单 类似FB的界面 MMDrawerController 一个侧滑控件 MFSideMenu Facebook风格侧滑 SWRevealViewController Facebook风格侧滑 DDMenuController 又是一个Facebook风格侧滑 REFrostedViewController 一个iOS7风格的毛玻璃侧滑菜单 RNFrostedSidebar 一个由侧面划出的半透明菜单(小圆片),不错的交互效果 Popover PopoverView CA实现的Popover 可用于iPhone,样式清新 WYPopoverController 高可定制Popover 样式不错,很棒 CMPopTipView 把PopView带入的iPhone FPPopover 一个PopOver 支持iPhone WEPopover 一个仿Popover API库 支持iPhone UAModalPanel 一个类似PopView控件 可以比较多的自定义 Demo样式较乱 ColorPopover 一个用WEPopover写的单色选择 MultiRowCalloutAnnotationView 在Map里面弹出带表格的Pop QBPopupMenu 类似iOS文字上的Pop 可以自定义~ kxmenu 一个PopView,动作还可以 RNGridMenu 一个Pop菜单,有背景模糊,效果不错~ GIKPopoverBackgroundView 为PopOver提供无缝背景 很棒 AwesomeMenu Path的扇形弹出按钮效果(CA) QuadCurveMenu 上面AwesomeMenu的fork 类似Path的菜单 MGTileMenu 弹出按钮菜单 在四周成方形 效果不错 ADPopupView 在UIVIew上弹出自己画的界面 挺丑 但可以看看实现 KLExpandingSelect 四叶草风格弹出菜单~ StackMenu Mac的Stack弹出菜单 下拉刷新 MSPullToRefreshController 一个不错的上下拉刷新的控件 里面是彩虹 EGOTableViewPullRefresh 下拉刷新,很早的一个控件了2年没更新了 PullToRefresh 下拉刷新 SVPullToRefresh 下拉刷新 扩展了UIScrollView 很方便简单 HybridRefreshGestureRecognizer 一个类似似TweetBot的下拉刷新样式 sspulltorefresh 下拉刷新 状态栏 JDStatusBarNotification 状态栏控件,支持iOS6、7 MTStatusBarOverlay 状态栏的控件,但很久没更新了 KGStatusBar 状态栏控件,提醒 GridView AQGridView 类似TableView但有横向分类,就像Android Launcher GMGridView 又一个GridView 很不错,自带横纵排序编辑等功能 KKGridView 又一个GridView 已经Deperacted 直接用系统的就好 A3GridTableView 又一个GridView 貌似支持类似图标样的上栏 LIExposeController 一个类似GridView的东西 Alert BlockAlertsAnd-ActionSheets 一个Block支持的Alert控件 支持自定义 不错~ PXAlertView 一个用来替代系统的Alert,高可定制,有iOS7风格 MBAlertView 一个简洁的AlertView,和HUD MJPopupViewController 用不同左右动画弹出AlertView MZFormSheetController 一个不错的弹出Alert Controller WCAlertView 一些不错的自定义AlertView GRAlertView 用CA画的带颜色的AlertView SBTableAlert 一个AlertView 可以带表格 可以用苹果风格 URBAlertView 自定义AlertView 还有各种动画 SIAlertView 一个样子不错的自定义AlertView MBMenuController 一个自定义AlertSheetVC JLActionSheet 另一个自定义AlertSheet ASDepthModal 实现简单的3维景深的模态视图 提供背景模糊 RNBlurModalView 将后方视图模糊 LMAlertView iOS7 可自定义内容的AlertView SDCAlertView iOS7 可自定义内容的AlertView Segment SVSegmentedControl 一个分段选择控件 类似Mac10.7测试版中的UI 很棒~ URBSegmentedControl 一个灵活的分段控件 可以上下 MCSegmentedControl 一个系统分段的子类 可以自定义颜色 AKSegmentedControl 完全自定义的分段控件 风格不错 HMSegmentedControl Chrome风格的Tabbar分段 SDSegmentedControl 一个离散并且凹陷效果的分段 PPiFlatSegmentedControl 扁平风格的Segment (包含iOS7样式) Switch DCRoundSwitch 仿UISwicher 可以自定义 很棒 TTSwitch 一个可以完全自定义的UISwich~~ KLSwitch iOS7风格的Switch 高仿 MBSwitch iOS7风格的Switch 效果多 SevenSwitch iOS7风格的Switch 效果很多~ Button CoolButtons 用CA画的带Glow的button 仿照系统 MAConfirmButton 一个类似AppStore下载的按钮 可以动画颜色大小等 FTWButton 一个UIControl实例的Button 可以看到如何自定义控件 有不错的动画效果 gradientbuttons 一个带有不同风格的Button~ 用CA绘制 DCActionButtonsController 一个PopOver的里面带各种画出的按钮 BPBarButtonItem 一个BarItem的不错的on the fly 生成 BButton 一种自定义Button 样式类似Twitter的Bootstrap 用CA画的 OBShapedButton 用CA绘制的特殊形状button,可以检测path touch UIGlossyButton 一些用CA画的带高光的奇怪Button... Scroll/Table LRSlidingTableViewCell 一个简单的左右滑动Cell 类似Twitter TISwipeableTableView UITable的左右滑动 类似Twitter TDBadgedCell 在TableCell右侧添加badge 有不同风格 UITableViewZoomController 类似Google+的 滑动Table缩放显示内部图片 UITableViewTricks TableView的扇形样式 iPhoneMK 几个简单的自己实现的View和Cell,提供某些常见功能 EasyTableView 一个支持左右的TableView 不错~ ZKRevealingTableViewCell 一个简单的可以左右滑动的TableCell ADLivelyTableView 非常棒的TableCell显示动画~~~ MHLazyTableImages 一个简单的Table加载网络图片 从Apple的例子中改的 MHPagingScrollView 一个可以Page的ScrollView 但可以看到左右的预览 类似WP7 KLScrollSelect 一个竖屏滚动的选择瀑布流 RNRippleTableView Cell梳子抖动~ 挺有创意 RETableViewManager 数据驱动的Table 由数据画出Table内容 ios-SDNestedTable 二级菜单的Table SWTableViewCell 类似iOS7邮件中的Cell,左右滑动出现多个功能键 TSUIKit 复杂数据表格、tab展示的UI控件,适合复杂数据展示 Notification EKNotifView 很简单的下方Notif实现 MKInfoPanelDemo 一个从上方滑下的Notif控件 类似TwieetBot TSMessages 在导航栏显示一个下拉Notif 效果不错 YRDropdownView 类似Tweetbot警告的下伸控件 NoticeView 类似Tweetbot的下伸控件 AJNotificationView 效果很不错的上部Notif提示 Label/Text MarqueeLabel UILabel跑马灯效果 ICTextView 文本查找,支持正则,关键词高亮 CoreTextHyperlinkView 用CoreText画的 可以带Link的Text EGOTextView UITextField替代品 支持富文本编辑 iOS-Rich-Text-Editor 富文本编辑 MTAnimatedLabel 类似滑动解锁的闪动Label TextGlowDemo 一个给Label发出Glow光亮的Demo FXLabel 很多不错的Label特效 AUIAnimatedText 一个UILabel子类 可以简单动画 颜色大小字体 RTLabel 简单的富文本Label 可以用Html样式 TTTAttributedLabel 一个Label控件 支持富文本属性 MDHTMLLabel 一个富文本Label控件,可以支持链接和点击事件 DTCoreText CoreText的包装,可以输入HTML~~ OHAttributedLabel 支持NSAttributedString的UILabel控件 ARLabel 自动字体大小 LEffectLabel 渐变动画颜色的Label,类似滑动解锁 BBCyclingLabel 在Label发生改变时,有动画效果 HTAutocompleteTextField TextField带自动补全 JVFloatLabeledTextField 带有能浮动的占位符的 文本框 CMHTMLView 用HTML展示富文本,但能有Native类似的体验 界面切换/导航 APExtendedPageController 类似Chrome左右滑动切换视图的控件 MBSpacialViewController 2D上下左右导航的奇怪视图 MSMatrixController 又一个 2D上下左右导航的奇怪视图.. MHTabBarController 一个类似Android的上方Tabbar FSVerticalTabBarController 在左侧的Tabbar AKTabBarController 自定义Tabbar 样式类似AppStore MGSplitViewController 类似系统的UISplitVC 但有更多功能 PSTCollectionView 类似系统的UIColloectionView 为了支持iOS4.3 JMTabView 一个TabView 用CA画的,类似 Mac系统的单选控件 MWFSlideNavigationViewController 一个能左右上下滑动的简单导航栏 UIScrollSlidingPages 一个和Chrome类似的左右滑动Pager FlipBoardNavigationController 类似网易客户端/Flipboard手机 左右滑动的Nav BCTabBarController Twitter类似的Tabbar 2年前的工程 CKSideBarController 类似Twitter iPad的左侧Tabbar DVSlideViewController 类似Safari的滑动切换 HGPageScrollView 类似Safari浏览的分页控件、 HSImageSidebarView 一个类似Keynote的侧边栏 RNSwipeViewController 滑动界面显示另一个界面 类似下拉状态栏查看天气 RESideMenu iOS7风格侧滑 知名效果Demo KLNoteViewController 一个类似Evernote旧版的Stack界面 绿色的 REComposeViewController 一个类似系统Twitter分享界面~ DETweetComposeViewController 一个iOS4实现的 类似iOS5的twitter分享 JTGestureBasedTableViewDemo 一个类似Clear的Demo Opaque 一个Clear的Demo MCSwipeTableViewCell 类似Mailbox的控件 和Clear差不多 CHTCollectionViewWaterfallLayout 瀑布流控件 PSCollectionView 类似瀑布流式的控件 IIIThumbFlow 瀑布流控件 WaterflowView 瀑布流的View 看样子Star挺多 KNPathTableViewController 在Table右侧显示类似Path的时间 很简单~ GSBookShelf 一个类似iBook书柜的实现 M6ParallaxController 类似Path封面的效果 ZGParallelView 类似Path的封面效果 貌似不错 TimeScroller 类似Path右侧的时间小滑块 chatheads Facebook 一个一直最上方的头像 点击可以显示Table MBSliderView 滑动解锁 DKLiveBlur 雅虎天气风格的动态模糊 ios-realtimeblur iOS7 模糊效果 GHSidebarNav 新版Facebook 侧滑等UI LXReorderableCollectionViewFlowLayout iBook中 拖动图书的功能 MDCParallaxView 景深效果 就像Path的封面那样 其他 SMPageControl 一个仿UIPageControl的API类 可以自定义小点点~ REActivityViewController 开的ActivityView实现 DCControls 不错的扇形滑动控件 平面风格 BSKeyboardControls 与键盘相关的控制 控制 上一项下一项 OCMapView 一个MapKit标注聚合用的东西~ NJKWebViewProgress 显示WebView加载进度 REMenu 一个导航栏下拉菜单 不错~ action-sheet-blocks 为ActionSSheet提供block方法 ADClusterMapView Map的地标Cluster 应该不错~ TPKeyboardAvoiding 一个UIView的 在有键盘时上移 calloutview 实现了私有的UICalloutView 类似地图中的弹出控件 SVPulsingAnnotationView 一个自定义MapLocator 动画控件 OWActivityViewController 一个类似系统的分享控件 SYEmojiPopover 显示一个Emoji选择de PopOVer fingertips 在屏幕上显示触摸点 只支持iPad2和iPhone4s以上 ASTouchVisualizer 为屏幕触摸添加指示 可用于屏幕录制 openspringboard 仿SpringBoard的东西 HMLauncherView 仿SpringBoard的东西 CQMFloatingController 一个浮动的VC 类似Popover(没有箭头) 可以用在横屏iPhone等 WSCoachMarksView 为某个控件加上蒙板 类似Spotlight KLHorizontalSelect 横向扫动选择 iOS-StyledPageControl 自定义PageControl,多种样式 iOS-blur iOS7的模糊效果 iOS7Only FXBlurView iOS7的模糊效果 可以用在iOS5以上 MYBlurIntroductionView 带有模糊背景效果的 Intro Masonry AutoLayout框架 KeepLayout AutoLayout框架 貌似比较好用 MLPAccessoryBadge 一些自定义badge~ JSBadgeView 数字角标 Badge MSCellAccessory Cell右侧的小箭头~ ios-fontawesome 一套用字体实现的Icon WTGlyphFontSet 另一套用字体实现的Icon DAKeyboardControl 键盘滑动消逝,就像系统内置的短信界面 JWFolders iOS6以下的文件夹展开效果 OCCalendar Popover样式的日期选择器 IBAForms Form构建。2年前的工程了.. ActionSheetPicker 一个PopOver里面是Picker MSCollectionViewCalendarLayout 日历事件样式 iOS6以上 MosaicUI WP7磁片风格View 自适应大小 MosaicLayout WP7磁片风格Layout UICollectionView iPhone-IntroductionTutorial 用于创建启动引导界面的... US2FormValidator 一套Table表单验证提示 AURosetteView 玫瑰花瓣似的展开菜单 EAIntroView App启动的介绍界面 InformaticToolbar 下边栏显示提示和动作 高级功能 PAPasscode 类似系统的输入密码界面 KKPasscodeLock 又一个类似系统输入密码界面 PTShowcaseViewController 能显示音视频等一堆格式文件的控件 Reader 一套显示PDF的库 支持大文件、加密文件 有iBook样式的界面 性能很好 FastPdfKit 一套PDF库 性能据说很好 也有界面 Kal 一套日历控件 高仿iOS默认 objc-TimesSquare 一套日历控件 类似iOS默认 貌似清新一些 ABCalendarPicker 高仿iOS默认日历的DatePicker~ MWPhotoBrowser 一套图片浏览控件 高仿iOS默认 支持网络图片 PTImageAlbumViewController 图片浏览控件 仿iOS默认 FGallery-iPhone 图片浏览器 AGImagePickerController 选取图片 支持多选 ELCImagePickerController 一个仿系统的ImagePicker 但支持多选 PhotoViewer 一个图片浏览的控件 KTPhotoBrowser 图片浏览控件 3年没更新了 DLCImagePickerController 一个用GPUImage实现的 ImagePicker功能,支持拍照和特效 route-me 一套自定义MapView 高仿系统 但支持不同的数据源 GrowingTextView 一个高仿短信发送的输入框 PHFComposeBarView 高仿短信发送输入框 MessagesTableViewController 高仿短信界面,可定制 AcaniChat 仿短信App 挺老了 UIBubbleTableView iOS短信的泡泡界面,可以支持图片 ODRefreshControl 一个高仿iOS6下拉刷新的控件 ssmessagesviewcontroller 和系统类似的发短信界面 挺粗糙 当然现在系统已经提供了 SVWebViewController 一个简易的浏览器功能控件 TSMiniWebBrowser 一个简易的内置浏览器 TITokenField 一个高仿Mail和短信 选择联系人的控件 DAAppsViewController 一个仿AppStore的列表界面 输入appId集合 WUEmoticonsKeyboard 输入表情的自定义键盘 CKCalendar 一个朴实的日历 LBYouTubeView 显示Youtube视频..国内无用 套装 tapkulibrary 一套比较大的自定义控件库 包括不错的HUD,进度条,日历,CoverFlow等 FlatUIKit 一套完整的扁平风格的UI (Flat) Weibo 一套仿iOS7的控件 UI7Kit 一套iOS7风格的控件,可以在iOS5、6上用 PrettyKit 一套系统UI的子类 有着平滑渐变和阴影效果 nui 一套加在UIKit上的Category,允许用类CSS代码来控制样式 QuickDialog 一套自定义控件库,用来快速建造基于Table的设置 MGBoxKit 一套UIView的扩展和子类(Table Grid..) 有不错的类CSS的功能 有些好用的代码 MGBox MGBox的旧版 应该去看v2 iOS-boilerplate 一些常见功能实现的例子:HUD Table滑动,打开URL用WebView等 挺老的工程 idev-recipes iDevRecipes博客的代码,演示了如何自定义一些控件,代码比较老.. AePubReader 一个阅读epub的 较完整的App Brushes 绘画的App,超棒,Appstore上架应用 Inkpad 一个完整的矢量绘图App,很棒,已上架 modizer !!!超强大的App 可以读取和演奏各种mod音乐 midi合成 解压 下载 自带庞大音乐库~ 已上架 kxmovie 一个ffmpeg的播放器 ~~~ 需要先构建ffmpeg ioctocat 一个Github客户端 cheddar-ios 一个日程管理的App 应该比较知名 AppSales-Mobile 基于iTunes Connect来查看App销售状况 canabalt-ios 一个跑步的小游戏~ baker 一套HTML5构建的ebook库 貌似挺强大 iPhoneTracker 一个挺老的App 2年前 Off-the-Record-iOS 一个实时聊天的功能 支持一些XMPP协议 kokuban 一个iPad小画板 3年前 glpaint 从Appled的GLPaint改的 OpenGLMilkyWay 银河~~ 很棒 有教材 iphone-app OSChina 开源中国 ..竟然挪到git.oschina.net去了。。 BeeFancy 一个仿Fancy的客户端 基于BeeFranework ruby-china-for-ios RubyChina官方客户端 ruby-china-ios 和上面一样? wh-app-ios 白宫官方App CastleHassle 一个Cocos2D的物理小游戏 貌似内容挺丰富~ DocSets-for-iOS iOS显示DocSets 可以下载iOS官方Doc~ newsyc 一个newsyc.me的客户端 (iPhone hacker News) News-YC---iPhone hackerNews的客户端 Upcoming 一个设计很棒的日程安排 Anypic 一个像Instagram那样的图片分享社区App ClassicMap 旧版iOS地图 ntlniph Twitter客户端 古老的工程了 lastfm-iphone Last.fm官方客户端 ThatInbox Ink出的邮件客户端 ThatCloud Ink出的云端文件管理 ThatPDF Ink出的PDF编辑 ThatPhoto Ink出的图片编辑 开发/调试工具 xctool iOS工程构建和测试工具,用于替代系统的xcodebuild (CI) PonyDebugger 一套Debugger工具,可以在电脑浏览器上远程调试iOS程序~查看试图层次、网络等 NSLogger 一套Log工具,可以在电脑上通过client实时查看NSLog,甚至是图片和二进制文件 支持Android KIF 一套用Private API做的调试库,可以以用户的方式测试(touch in xxx) gh-unit 一套ObjC的测试框架 DCIntrospect 一套iOS调试工具,在iOS界面里显示信息 CocoaLumberjack 一套类似Log4j的东西,可提供企业级的Log~~~~ Frank 自动化测试工具? superdb 一个Debuger工具 用命令行调试。。 iOS-Hierarchy-Viewer 一个调试iOS界面的工具 通过浏览器访问~~ 值得瞅瞅~~ hierarchydetective 一个3D显示iOS视图层级的调试工具 CATransform3D-Test 调试CATransform3D矩阵的工具~ LLDB-QuickLook 在调试时用命令行调用QuickLook来显示图片、UIView等 iConsole 在App内显示调试信息、执行调试命令 Nocilla 一个模拟HTTP响应的调试库 dyci-main 一个可以在调试时动态添加代码的工具 需要改动Xcode GestureLab 调试和把玩GestureRecognizor的 cedar BDD风格单元测试 iOS-UI-Assets iOS5.1中,系统控件和App的图片资源Dump 嗯..iOS7后就没用了 iOS-Artwork-Extractor 导出iOS系统App及控件的图片资源 只支持到iOS5 旧代码 & 奇怪的东西 DB5 通过plist构建界面的小东西 HockeyKit 一套iOS的 Ad-Hoc升级框架.包含PHP服务端 Briefs 一套类似原型制作工具 OpenTLD 视频跟踪检测 貌似挺高深 SimFinger 一些Fake系统图标的App 还有一些设备外框的图片、触摸点图片可以用 scifihifi-iphone 已经弃用的旧代码 新工程去看github页面 iphonearkit 4年前的代码 增强现实 (AR) TouchDB-iOS 一个嵌入式的CouchDB实现 iOS-Runtime-Headers 从运行时Dump出来的公有/私有 Framework头 通过这个可以进行不同版本的对比 objectiveresource RoR相关的东西 GRMustache Mustache模板? titanium_modules Titanium 的常用模块 fontdiao 类似FontAwesome的东西,包含了一些国内网站logo 代码库大包包 iphone-3.0-cookbook- 随书代码 iOS-5-Cookbook 随书代码~ iOS-6-Cookbook iOS-6-Advanced-Cookbook iOS7-day-by-day iOS7-day-by-day 博客的代码 iOS7-Sampler iOS7的一些新功能演示~ MyProjects 一些http:/ /mysparks.info上面的代码 Xcode-Snippets 一些Xcode代码片段 Mac Only gitx 一个git的图形客户端 Chameleon 把UIKit克隆到Mac里 Induction 一个Mac上的数据库客户端 支持Mysql Postgre Nosql Redis等 twui 一个Mac的自定义控件库,有类似iOS的Table和Tab,CA实现 CocoaPods 一个Ruby程序,用来管理Cocoa库依赖 kod Mac上的代码编辑,Chrome风格,小巧快速 slate Mac上窗口管理 nu 一个包装,用nu语言来写东西? appledoc 用来生成Apple格式的文档和网页 macgap 包装以可以用类似js的写法来调用函数 Quicksilver 这个..Mac上知名的快速启动 mogenerator 生成CoreData的Code nv Note程序 iTerm2 知名终端~ Sparkle 一个软件升级用的Framework PostgresApp PostgresSQL客户端 terminal-notifier 通过终端发送Notification ShiftIt 管理桌面窗口 状态栏插件 textmate 这个不用说了。。~~~ textmate-missingdrawer Textmate插件 一个侧边栏 AckMate Textmate插件 运行ack? clicktoflash Webkit插件 屏蔽Flash MongoHub-Mac MongoDB的客户端 CocosBuilder 用于cocos2d的JS绑定 GUI设计 Rebel 一些AppKit的扩展 Textual 轻量的IRC客户端 vico 一个开发用文本编辑器 pomodoro 一个时间管理的App visor Terminal的插件 GoAgentX 这个不用说了.... Pixen 像素画 的工具 DataKit 不知道什么好想是与数据和网络连接的? Color-Picker-Pro 取色器 状态栏插件 SNRHUDKit Mac版的HUD ConnectionKit FTP和WebDAV OpenEmu 各种游戏机模拟器 Hacky 一个完整的Hacker News客户端~~ WWDCDownloader 下载WWDC用,Xcode5 only XVim Xcode的插件 支持绑定VIM快捷键 QuickCast 一个录屏分享的App zephyros 窗口管理 for hackers shuttle SSH快捷方式 菜单栏插件 qlstephen 系统QuickLook的插件,查看无扩展名的文本文件 INAppStoreWindow Mac AppStore风格的NSWindow spectacle 窗口管理 不用鼠标 limechat IRC客户端 LiveReload Web开发 selfcontrol 自控。。禁止上网一段时间 Popup 在状态栏弹出的Popup MPlayerX 一个MPlayerX的测试分支 QuickCursor 快速启动编辑 Sonora 一个小巧的 挺棒的音乐播放器 SafariOmnibar 一个Sarari插件 HexFiend 16进制编辑器 Xcode5-Plugin-Template Xcode5插件的开发模板 cocoapods-xcode-plugin cocoapod 插件 KFCocoaPodsPlugin cocoapod 插件, 方便编辑Podfile,显示构建日志 VVDocumenter-Xcode Xcode的插件 写注释时自动提醒JavaDoc风格 ColorSense-for-Xcode Xcode的插件 支持动态调整UIColor KSImageNamed-Xcode Xcode的插件 支持显示工程里的UIImage Lin Xcode的插件 显示NSLocalizedString的对应字符串 nib2objc 把xib和nib翻译成m 包括命令、GUI、Service iOS-Framework 编译iOS的Framework的通用模板 Alcatraz 管理和发现插件~~ UIEffectDesignerView 原生iOS/Mac粒子效果设计 SCStringsUtility 开发工具 制作多语言 APNS-Pusher 通过APNS发推送~ 非Github cooliris-toolkit Cooliris出品基础工具包,Github有Clone core-plot 强大的2D数据绘图库 j2objc 神奇的东西,将Java翻译成ObjC~ Google出品 theunarchiver 解压,支持Zip, Tar, Gzip, Bzip2, 7-Zip, Rar, LhA, StuffIt等 macfuse 创建OSX的文件系统~ google-toolbox-for-mac 著名的Google工具箱 leveldb Google大神开发的KV存储,高性能,低内存 RegexKitLite 著名的ObjC 正则表达式 zipachive 用得很广泛的ObjC zip文件压缩解压 (github上有几个mirror)youtube下载神器:https://github.com/rg3/youtube-dl我擦咧vim插件:https://github.com/Valloric/YouCompleteMevim插件配置:https://github.com/spf13/spf13-vim----------------Mac完整项目----------电台:https://github.com/myoula/sostart----------------iOS完整项目----------------1,豆瓣相册 https://github.com/TonnyTao/DoubanAlbum2,voa在线英语 https://github.com/cubewang/NewsReader3,电竞第一视角 https://github.com/cubewang/GameDaily4,开源中国的iOS客户端 https://github.com/oschina/iphone-app5,很优雅的一些组件 https://github.com/sobri909/MGBox26,ios控件学习:https://github.com/iimgal/StudyiOS?source=c7,reader :https://github.com/vfr/Reader8,git客户端: https://github.com/dennisreimann/ioctocat9,speakEnglish:https://github.com/cubewang/SpeakEnglish 克伟10,新闻阅读 :https://github.com/samuelclay/NewsBlur11,last.fm:https://github.com/c99koder/lastfm-iphone12,LBS游戏:https://github.com/Kjuly/iPokeMon13,ThatInbox 是iOS平台上一个免费开源的Email 客户端:https://github.com/Ink/ThatInbox14,ThatCloud是一个免费开源的iOS app,允许你访问、查看以及使用你在网上存储的内容,可以很好地帮你完成工作:https://github.com/Ink/ThatCloud15,ThatPhoto是使用了Ink Mobile Framework框架来连接到其他iOS 应用程序,你可以用它来编辑和管理照片:https://github.com/Ink/ThatPhoto16,ThatPDF一个开源的,用来阅读、签名和注解PDF 文档的工具:https://github.com/Ink/ThatPDF17,xmpp聊天系统:https://github.com/chrisballinger/Off-the-Record-iOS18,对口袋NCE有用的app:https://github.com/imtiger/HappyEnglish 19,一个像Instagram那样的图片分享社区App:https://github.com/ParsePlatform/Anypic20,黑客阅读:https://github.com/mmackh/Hacker-News-for-iOS21,画图软件:https://github.com/sprang/Brushes 22,APN软件:https://github.com/lexrus/APN.iOS23,Sol的天气app:https://github.com/comyarzaheri/Sol24,货币转换:https://github.com/nicklockwood/Concurrency25,来电归属地查询的软件(不能上架):https://github.com/Quotation/WhoCall26,Mogo iOS 客户端:https://github.com/jurre/Mogo-iOS27,https://github.com/AshFurrow/C-4128,使用XMPP协议的IM开源软件:https://github.com/chrisballinger/ChatSecure-iOS29,WWDC:https://github.com/indragiek/WWDC-201430,移动支付公司 Square 将去年收购的照片应用 Viewfinder 开源了,包含服务端、iOS 和 Android 应用代码:https://github.com/viewfinderco/viewfinder31,圣经小助手 :https://github.com/nixzhu/Bible-Assistant32,已阅 :https://github.com/ming1016/RSSRead33,美国白宫APP:https://github.com/WhiteHouse/wh-app-ios34,Ruby for China: https://github.com/ruby-china/ruby-china-for-ios35,breadwallet iOS bitcoin wallet :https://github.com/voisine/breadwallet36,品趣:https://github.com/novel-design/novel-design37,懒人笔记:https://github.com/liaojinxing/Voice2Note38, Doppio :https://github.com/chroman/Doppio----------------开源项目的协议--------------网易新闻的开源协议: http://m.163.com/special/newsclient/ios_libraries.html----------------组件----------------自定义tabbar(1)的: https://github.com/i300/TweetBotTabBar自定义tabbar(2)和上拉刷新:http://www.cocoachina.com/bbs/read.php?tid=62061&keyword=tabbar自定义tabbar(3):https://github.com/jinthagerman/JBTabBarController类似instagram的tabbar:https://github.com/boctor/idev-recipes/tree/master/CustomTabBarNotificationcoretext:https://github.com/Cocoanetics/DTCoreText图片延时加载:http://developer.apple.com/library/ios/#samplecode/LazyTableImages网络请求:https://github.com/pokeb/asi-http-requesthttps://github.com/AFNetworking/AFNetworkingJson解析:https://github.com/johnezang/JSONKit图片异步加载:https://github.com/rs/SDWebImage?source=c瀑布流1):https://github.com/aceisScope/WaterflowView瀑布流2)http://code4app.com/ios/%E7%80%91%E5%B8%83%E6%95%88%E6%9E%9C-%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/4fdfecd96803fa117f000000瀑布流3):https://github.com/chiahsien/CHTCollectionViewWaterfallLayout 瀑布流4):http://www.cocoachina.com/bbs/read.php?tid=94851&keyword=%C6%D9%B2%BC%C1%F7瀑布流5):https://github.com/ptshih/PSCollectionView 瀑布流6):http://www.cocoachina.com/bbs/search.php?ss=index#submit 搜索:瀑布流瀑布流7):https://github.com/steipete/PSTCollectionView做图书的一个框架:https://github.com/Simbul/baker项目内文档:https://github.com/tomaz/appledoc?source=c抽屉导航:https://github.com/Inferis/ViewDeckiOS的url router :https://github.com/gaosboy/urlmanagerhttps://github.com/usepropeller/routable-ioshttps://github.com/jverkoey/sockit照片墙:https://github.com/gmoledina/GMGridViewcollectionview:https://github.com/steipete/PSTCollectionView自动更新类:https://github.com/lexrus/LTUpdate官网蝴蝶的OpenGL应用:https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITabBar_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40007521-CH3-SW4开机密码锁:https://github.com/aporat/KKPasscodeLock视频播放器:https://github.com/blizzard-op/VideoPlayerKit音频播放完整客户端:https://github.com/kstenerud/ObjectAL-for-iPhone豆瓣音频播放:https://github.com/douban/DOUAudioStreamer 掉渣天的音频播放流:https://github.com/alexbw/novocaineAudioEngine:https://github.com/TheAmazingAudioEngine/TheAmazingAudioEngine 1000+滑动的自定义的SegmentedControl控件:https://github.com/samvermette/SVSegmentedControl自定义的segmented:https://github.com/HeshamMegid/HMSegmentedControl类似swipelist的左右滑动的cell:https://github.com/alikaragoz/MCSwipeTableViewCell 扩展1:https://github.com/soffes/sstoolkit 扩展2:(存用户密码):https://github.com/soffes/sskeychain scrollview自动滚动:https://github.com/shanegao/SGFocusImageFramescrollview左右滑动,渐隐渐出:https://github.com/park0ur/Path-Intro-iPhonescrollview滑动条变细:https://github.com/r-plus/ScrollThindicator弹窗层:https://github.com/martinjuhasz/MJPopupViewController tableview下拉图片放大:https://github.com/hunk/TwProfile选取iPod库播放音乐:https://github.com/gangverk/GVMusicPlayerControllerUIView动画:https://github.com/neror/ftutils类似clear的超强动画cell:https://github.com/mystcolor/JTGestureBasedTableViewDemo网易新闻背景图浮动:https://github.com/kenshin03/Home-For-iOS仿网易,新浪的push效果,带有阴影:1)http://code4app.com/ios/%E8%A7%86%E5%9B%BE%E5%88%87%E6%8D%A2%E5%A4%A7%E5%B0%8F%E6%B8%90%E5%8F%98%E6%95%88%E6%9E%9C/5124399a6803fae82c0000002)https://github.com/vinqon/MultiLayerNavigation3)https://github.com/chisj/EuPopDemo实现原理:http://mobile1.riaos.com/?p=2025414新浪微博和朋友圈的图片浏览方式:1)https://github.com/Seitk/FB-Gallery2)https://github.com/jimneylee/SinaMBlogNimbuscoretext 运用:https://github.com/akosma/CoreTextWrapperN多自定义组件:https://github.com/boctor/idev-recipes 可以换图片的pageControl:https://github.com/Spaceman-Labs/SMPageControl瀑布流:https://github.com/ptshih/PSCollectionViewiOS7的扁平UI:https://github.com/Grouper/FlatUIKit产品引导view:https://github.com/123nobody/WZGuideViewController类似safari的页面浏览:https://github.com/100grams/HGPageScrollView 正则匹配UIView :https://github.com/KayK/RegexHighlightView 加密措施:https://github.com/dev5tec/FBEncryptorhttps://github.com/mayurbirari/AES256AndBase64自增长的键盘:https://github.com/HansPinckaers/GrowingTextView自定义的map Annotation :https://github.com/grgcombs/MultiRowCalloutAnnotationView超屌的3d画面:https://github.com/nicklockwood/iCarousel 上拉刷新 : https://github.com/dbsGen/SlimeRefresh翻页效果:https://github.com/brow/leaves类似maps的半截翻页:https://github.com/FairfaxMobile/FDCurlViewControl弹窗:https://github.com/martinjuhasz/MJPopupViewController对话框:https://github.com/jessesquires/MessagesTableViewControllerpagecurl的翻页效果:http://www.cocoachina.com/bbs/read.php?tid=11856&keyword=%B5%D8%CD%BC二维码:http://www.cocoachina.com/applenews/devnews/2013/0104/5462.html自定义的annotation(1):https://github.com/applidium/ADClusterMapView自定义的annotation(2):http://code4app.com/ios/Customized-Complex-Map-Annotation/4f67f4f86803fa843f000002#osc类似国家地理的翻页:https://github.com/michaelhenry/MHNatGeoViewControllerTransitioniOS开发私有库:https://github.com/kennytm/iphone-private-frameworksviewController的切换(从右边进来):https://github.com/steipete/PSStackedView 安全存储用户名,密码等:https://github.com/granoff/LockboxFormSheet的神器:https://github.com/m1entus/MZFormSheetControlleriOS中model类的写法:https://github.com/github/Mantle相关介绍:https://github.com/blog/1299-mantle-a-model-framework-for-objective-cmodel类解析:https://github.com/nicklockwood/BaseModelbasemodel解析:https://github.com/andrep/RMModelObject蓝牙:https://github.com/xuanhuangyiqi/Anti-Lostmodel类比较好的库1):https://github.com/icanzilb/JSONModelmodel类比较好的库2):https://github.com/github/Mantle原生App的远程调试工具包:https://github.com/square/PonyDebugger#remote-logging将缓存存在磁盘:https://github.com/rs/SDURLCachepath的欢迎页实现方式:https://github.com/icepat/ICETutorial path的左下角菜单栏的同类实现方法:https://github.com/mattgemmell/MGTileMenucoredata的封装库:https://github.com/magicalpanda/MagicalRecord coredata的 sql使用方式:https://github.com/marcoarment/FCModelzip文件解压缩:https://github.com/soffes/ssziparchive完美的图片category:https://github.com/Nyx0uf/NYXImagesKit纵向的scrollview循环利用:https://github.com/andreyvit/SoloComponents-iOSUINavigationController的push可能导致错误的效果的补充:https://github.com/Plasma/BufferedNavigationControllerUIKit的分类:https://github.com/enormego/cocoa-helpers 左右滚动的scrollview(类似网易读图):https://github.com/kejinlu/PagedFlowViewwebview控制器:https://github.com/samvermette/SVWebViewController滑动帧动画:https://github.com/IFTTT/JazzHands iOS6的水滴下拉刷新:https://github.com/Sephiroth87/ODRefreshControl微信下拉显示logo:https://github.com/gluttony/RevealLogo支付宝的锁屏界面:https://github.com/kejinlu/KKGestureLockViewstatus bar的离线loading效果:http://www.cocoachina.com/bbs/read.php?tid=99947&keyword=%CD%F8%D2%D7模仿百度地图向下推的层:https://github.com/mariohahn/MHDismissModalViewFileManager:https://github.com/nicklockwood/StandardPaths下拉头图放大:https://github.com/cyndibaby905/TwitterCover(1)访网易的左右滑动的view:http://code4app.com/ios/%E4%BB%BF%E7%BD%91%E6%98%93%E6%96%B0%E9%97%BB%E7%9A%84%E6%BB%91%E5%8A%A8%E8%A7%86%E5%9B%BE%E5%B8%83%E5%B1%80/5255fbd96803fa8660000000(2)左右横向滑动:https://github.com/MugunthKumar/MKHorizMenuDemotableview模仿path的时间表:https://github.com/kentnguyen/KNPathTableViewController弹出层:https://github.com/50pixels/FPPopover二维码登录:https://tiqr.org/弹出层:https://github.com/UrbanApps/UAModalPanelFacebook paper中的图片左右移动实现:https://github.com/chroman/CRMotionView横向滚动的tab1):https://github.com/pppoe/LightMenuBar横向滚动的tab2):https://github.com/MugunthKumar/MKHorizMenuDemo左右滑动的cell,有删除等功能:https://github.com/CEWendel/SWTableViewCell/不同速度的scrollview,用于做启动介绍页:https://github.com/5sw/SWParallaxScrollViewmattt写的解析html和xml的:https://github.com/mattt/Ono通过服务器动态修改客户端逻辑:https://github.com/mmin18/WaxPatchroutes,可以让按钮直接向点html的链接一样:https://github.com/joeldev/JLRoutes像新浪微博一样的图片浏览viewcontroller:https://github.com/jaredsinclair/JTSImageViewController像微信一样的search bar :https://github.com/fabiankr/TableViewSearchBar图片浏览:https://github.com/EddyBorja/EBPhotoPages左滑动或右滑动cell:https://github.com/modocache/MDCSwipeToChoose类似iOS相册删除照片的动画效果:https://github.com/Ciechan/BCGenieEffect扁平化的uibutton:https://github.com/barbosa/GBFlatButton给app评分的组件1:https://github.com/nicklockwood/iRate给app评分的组件2:https://github.com/arashpayan/appiraterNSFileManager的封装:https://github.com/fabiocaccamo/FCFileManagerpaper的点击展开效果:https://github.com/hebertialmeida/HAPaperViewController柱状图:https://github.com/honcheng/iOSPlot点击titleview弹出下拉的menu:https://github.com/romaonthego/REMenu自定义开场图:https://github.com/ealeksandrov/EAIntroView让navbar跟着scrollview一起滚动:https://github.com/andreamazz/AMScrollingNavbar跟相册相关的:https://github.com/B-Sides/ELCImagePickerController更高效的显示地图上的大头针:https://github.com/choefele/CCHMapClusterControllerstreaming的音频播放:https://github.com/douban/DOUAudioStreamerXMPP的使用:https://github.com/adow/DollarssiOS7 mailbox的statusbar运用:https://github.com/simonholroyd/StatusBarTest国家地理的页面切换动画:https://github.com/michaelhenry/MHNatGeoViewControllerTransition引导页1:https://github.com/MatthewYork/MYBlurIntroductionView引导页2:https://github.com/MatthewYork/iPhone-IntroductionTutorial引导页3:https://github.com/ealeksandrov/EAIntroViewiOS7的电话button: https://github.com/mrcrow/MRoundedButton类似paper的导航抖动:https://github.com/andreamazz/AMWaveTransition下拉填充满字体:https://github.com/d-ronnqvist/blogpost-codesample-PullToRefresh:gif播放 :https://github.com/Flipboard/FLAnimatedImage视图切换:https://github.com/zoonooz/ZFDragableModalTransition类似游戏的菜单按钮,点击伸缩:https://github.com/sendoa/QBKOverlayMenuView模仿twitter的首页左右切换:https://github.com/duowan/TwitterPaggingViewer下载器:https://github.com/thibaultCha/TCBlobDownload网易新闻的离线下载bar:https://github.com/jaydee3/JDStatusBarNotification图片剪切和拆剪:https://github.com/kishikawakatsumi/PEPhotoCropEditor过度效果的status bar :https://github.com/nrj/AlphaGradientStatusBarFacebook’s paper的弹出层:https://github.com/UrbanApps/UAModalPanelObjective-C 和JavaScript 交互:https://github.com/marcuswestin/WebViewJavascriptBridgeCollectionView in UITableViewCell:https://github.com/AshFurrow/AFTabledCollectionViewpopview(1):https://github.com/jmascia/KLCPopuppopview(2)https://github.com/andreamazz/AMPopTipUICollectionView replacement of UITableView (添加上section):https://github.com/jamztang/CSStickyHeaderFlowLayoutstorekit: https://github.com/mattt/CargoBay========================== UICollectionView相关===============https://github.com/bryceredd/RFQuiltLayout==========================helper相关===============https://github.com/andrewroycarter/UIView-Helpers==========================auto layout相关=======https://github.com/cloudkite/Masonryhttps://github.com/smileyborg/UIView-AutoLayout https://github.com/iMartinKiss/KeepLayoutpinterst的下拉刷新填充满的效果:https://github.com/uzysjung/UzysCircularProgressPullToRefresh==========================block相关=======================================https://github.com/jivadevoe/UIAlertView-Blockshttps://github.com/pandamonia/BlocksKit ==========================iOS7相关=======================================iOS7颜色类:https://github.com/claaslange/iOS7ColorsiOS7:动态毛玻璃效果:https://github.com/alexdrone/ios-realtimebluriOS7 blur侧边栏:https://github.com/rnystrom/RNFrostedSidebariOS7 blur image:https://github.com/lukabernardi/LBBlurredImageiOS7 blur 效果:https://github.com/nicklockwood/FXBlurViewiOS7的uialertview:https://github.com/alexanderjarvis/PXAlertView边打字边出现标题:https://github.com/jverdi/JVFloatLabeledTextField扁平化的segment:https://github.com/pepibumur/PPiFlatSegmentedControliOS7视图切换炫酷效果:https://github.com/ColinEberhardt/VCTransitionsLibraryiOS7教学代码:https://github.com/ShinobiControls/iOS7-day-by-dayiOS7风格的抽屉导航:https://github.com/monospacecollective/MSDynamicsDrawerViewController颜色渐变的加载:https://github.com/nrj/GradientProgressViewiOS7demo:https://github.com/shu223/iOS7-SampleriOS正则分类:https://github.com/bendytree/Objective-C-RegEx-CategoriesiOS7侧边栏:https://github.com/romaonthego/RESideMenuiOS7侧边栏(覆在view上)https://github.com/romaonthego/REFrostedViewControlleriOS7库:https://github.com/youknowone/UI7Kit(可以让iOS5也有iOS7的样子)===========================测试框架=====http://www.cocoachina.com/applenews/devnews/2013/1025/7242.htmlhttps://github.com/kif-framework/KIF
前言 几年前笔者是使用Objective-C进行iOS开发, 不过在两年前Apple发布swift的时候,就开始了swift的学习, 在swift1.2发布后就正式并且一直都使用了swift进行iOS的开发了, 之后就是对swift持续不断的学习, 近来swift3.0的发布, 更多的人会选择swift来进行iOS的开发看上去更是成为了一种趋势, 不过一个合格的iOS开发者对oc以及c语言的掌握是必不可少的技能, 本篇中主要是写一些大家平时都可能用到但是不一定知道的oc的东西 oc中的对象的创建: 首先会通过 +(id)alloc 动态的分配所有的变量以及父类定义的变量所需要的足够内存, 同时会清除所有的分配的内存空间, 全部置为0 同时接着需要调用class的 -(id)init 方法, 这个方法给每个变量设置初始值 返回的类型为id, id是一个可以指向任意类型的指针(不用 * 号), 这个在一定程度上可以完成多态的效果 对oc中的class文件的理解: class, extension, category ZJPerson.h文件 Snip20160817_4.png ZJPerson.m文件 Snip20160817_5.png ZJPerson.m文件 m [[XXObject alloc] init] 初始化方法不需要参数的时候, 和 [XXObject new] 方法相同 通过字面量来初始化对象, 例如 NSString *string = @"string"; == [[NSString alloc] initWithString:@"string"];等初始化方法 NSNumber *myBOOL = @YES; == [[NSNumber alloc] initWithBool:YES]; NSNumber *myFloat = @3.14f; == NSNumber *myInt = @42; == NSNumber *myLong = @42L; ==... oc(c)中多行宏的定义(这个在swift...中更方便直接一个全局的函数就搞定了): 在除了最后一行的每一行结尾加一条反斜杠 \ 定义.png 使用.png 比较是否相同: 使用 if(a==b) {}, 如果a,b是对象类型, 那么比较的是指针是否相同, 而不是比较值是否相同, 如果a, b是基本类型(int, double...), 那么比较的是值是否相同; 使用if ([a isEqual: b]) { }, 则比较的是a,b的值是否相同 初始化基本类型的时候尽量设置初始值, 因为编译器分配的初始值并不确定, 但是对象类型会默认初始化为nil 条件判断: 当对象不为nil(有内存地址)的时候, 或者基本类型非0, 或者bool类型为true, 这个时候条件都为真, 其他情况条件为假 oc中属性的getter和setter@property (nonatomic) NSString *name; 例如当有这样一个name属性的时候, 默认是readWrite的, 编译器会自动生成一个set (setName:)和get(-(NSString *)name)方法, 这个时候可以通过set或者get方法访问到name, 如果申明为(readonly), 那么将只会生成get方法 [self setName:@"set name"]; NSString *getName = [self name]; 也可以通过点语法访问(实际上是会自动调用set和get方法) self.name = @"set name"; NSString *dotName = self.name; 同时你可以重写name的get(懒加载...)和setter(拦截set方法)... 对应name属性, 编译器会生成(synthesize)一个 _name 允许我们直接通过指针访问变量, 而不会调用get方法, 所以通过_xx访问的变量不会调用懒加载(get方法), 所以在写懒加载方法的时候, 不能使用self.xx(造成死循环), 而要使用_xx - (NSString *)name { // 这里面不能使用self.name , 因为点语法会调用这个get方法, 造成死循环 if (_name == nil) { _name = @"name"; } return _name; } 同时这个synthesize的名字我们是可以自己修改的, 使用如下的语法@synthesize name = customName; 那么这个时候就不能通过 __name访问到name了, 因为我们已经指定了通过customName才能访问到了NSString *getName = customName; 当然如果, 你是这样写的 @synthesize name;, 并没有指定名字, 这个时候访问的时候就直接使用变量名而不需要加下划线( _ )了 name = @"set name";
iOS中的HotFix方案总结详解 相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结。iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dynamic Framework(Apple) React Native(Facebook) JSPatch(Tencent) WaxPatch WaxPatch是一个通过Lua语言编写的iOS框架,不仅允许用户使用 Lua 调用 iOS SDK和应用程序内部的 API, 而且使用了 OC runtime 特性调用替换应用程序内部由 OC 编写的类方法,从而达到HotFix的目的。 WaxPatch的优点在于它支持iOS6.0,同时性能上比较的优秀,但是缺点也是非常的明显,不符合Apple3.2.2的审核规则即不可动态下发可执行代码,但通过苹果JavaScriptCore.framework或WebKit执行的代码除外;同时Wax已经长期没有人维护了,导致很多OC方法不能用Lua实现,比如Wax不支持block;最后就是必须要内嵌一个Lua脚本的执行引擎才能运行Lua脚本;Wax并不支持arm64框架。 Dynamic Framework 动态的Framework,其实就是动态库;首先我介绍一下关于动态库和静态库的一些特性以及区别。 不管是静态库还是动态库,本质上都是一种可执行的二进制格式,可以被载入内存中执行。iOS上的静态库可以分为.a文件和.framework,动态库可以分为.dylib(xcode7以后变成了.tdb)和.framework。 静态库: 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。 动态库: 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。 静态库和动态库是相对编译期和运行期的:静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。 总结:同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit、Foundation等),所以程序体积会小很多。 好,所以Dynamic Framework其实就是我们可以通过更新App所依赖的Framework方式,来实现对于Bug的HotFix,但是这个方案的缺点也是显而易见的它不符合Apple3.2.2的审核规则,使用了这种方式是上不了Apple Store的,它只能适用于一些越狱市场或者公司内部的一些项目使用,同时这种方案其实并不适用于BugFix,更适合App线上的大更新。所以其实我们项目中的引入的那些第三方的Framework都是静态库,我们可以通过file这个命令来查看我们的framework到底是属于static还是dynamic。 React Native React Native支持用JavaScript进行开发,所以可以通过更改JS文件实现App的HotFix,但是这种方案的明显的缺点在于它只适合用于使用了React Native这种方案的应用。 JSPatch JSPatch是只需要在项目中引入极小的JSPatch引擎,就可以使用JavaScript语言调用Objective-C的原生接口,获得脚本语言的能力:动态更新iOS APP,替换项目原生代码、快速修复bug。但是JSPatch也有它的自己的缺点,主要在由于它要依赖javascriptcore,framework,而这个framework是在iOS7.0以后才引入进来,所以JSPatch是不支持iOS6.0的,同时由于使用的是JS的脚本技术,所以在内存以及性能上面是要低于Wax的。 所以最后当然还是采用了JSPatch这种方案,但是实际过程中还是出现了一些问题的,所以掌握JSPatch的核心原理对于我们解决问题是非常有帮助的。 关于JSPatch的核心原理讲解 预加载部分 关于核心原理的讲解,网上有不少,但是几乎都是差不多,很多都还是引用了作者bang自己写的文档的内容,所以我采用一个例子方式进行讲解JSPatch的主要运行流程,其实当然也会引用一些作者的简述,大家可以参照我写的流程讲述,在配合源码或者官方文档的介绍,应该就可以了解JSPatch。 [JPEngine startEngine]; NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; [JPEngine evaluateScript:script]; 首先是运行[JPEngine startEngine]启动JSPatch,启动过程分为一下两部分: 通过JSContext,声明了一些JS方法到内存,这样我们之后就可以在JS中调用这些方法,主要常用到的包括以下几个方法,同时会监听一个内存告警的通知。 context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) { return defineClass(classDeclaration, instanceMethods, classMethods); }; context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { return callSelector(nil, selectorName, arguments, obj, isSuper); }; context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { return callSelector(className, selectorName, arguments, nil, NO); }; context[@"_OC_formatJSToOC"] = ^id(JSValue *obj) { return formatJSToOC(obj); }; context[@"_OC_formatOCToJS"] = ^id(JSValue *obj) { return formatOCToJS([obj toObject]); }; context[@"_OC_getCustomProps"] = ^id(JSValue *obj) { id realObj = formatJSToOC(obj); return objc_getAssociatedObject(realObj, kPropAssociatedObjectKey); }; context[@"_OC_setCustomProps"] = ^(JSValue *obj, JSValue *val) { id realObj = formatJSToOC(obj); objc_setAssociatedObject(realObj, kPropAssociatedObjectKey, val, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }; 加载JSPatch.js文件,JSPatch文件的主要内容在于定义一些我们之后会用在的JS函数,数据结构以及变量等信息,之后我会在用到的时候详细介绍。 脚本运行 我们定义如下的脚本: require('UIAlertView') defineClass('AppDelegate',['name', 'age', 'temperatureDatas'], { testFuncationOne: function(index) { self.setName('wuyike') self.setAge(21) self.setTemperatureDatas(new Array(37.10, 36.78, 36.56)) var alertView = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( "title", self.name(), self, "OK", null) alertView.show() } }, { testFuncationTwo: function(datas) { var alertView = UIAlertView.alloc().initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( "title", "wwww", self, "OK", null) alertView.show() } }); 然后就是执行我们上面说的[JPEngine evaluateScript:script]了,程序开始执行我们的脚本,但是在这之前,JSpatch会对我们的脚本做一些处理,这一步同样也包括两个方面: 需要给我们的程序加上try catch的部分代码,主要目的是当我们的JS脚本有错误的时候,可以catch到错误信息 将所有的函数都改成通过__c原函数的形式进行调用。 也就是最后我们调用的脚本已经变成如下的形式了: ;(function(){try{require('UIAlertView') defineClass('AppDelegate',['name', 'age', 'temperatureDatas'], { testFuncationOne: function(index) { self.__c("setName")('wuyike') self.__c("setAge")(21) self.__c("setTemperatureDatas")(new Array(37.10, 36.78, 36.56)) var alertView = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")( "title", self.__c("name")(), self, "OK", null) alertView.__c("show")() } }, { testFuncationTwo: function(datas) { var alertView = UIAlertView.__c("alloc")().__c("initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles")( "title", "wwww", self, "OK", null) alertView.__c("show")() } }); 那么为什么需要用函数__c来替换我们的函数呢,因为JS语法的限制对于没有定义的函数JS是无法调用的,也就是调用UIAlertView.alloc()其实是非法的,因为它采用的并不是消息转发的形式,所以作者原来是想把一个类的所有函数都定义在JS上,也就是如下形式: { __clsName: "UIAlertView", alloc: function() {…}, beginAnimations_context: function() {…}, setAnimationsEnabled: function(){…}, ... } 但是这种形式就必须要遍历当前类的所有方法,还要循环找父类的方法直到顶层,这种方法直接导致的问题就是内存暴涨,所以是不可行的,所以最后作者采用了消息转发的思想,定义了一个_c的原函数,所有的函数都通过_c来转发,这样就解决了我们的问题。 值得一提的是我们的__c函数就是在我们执行JSPatch.js的时候声明到js里的Object方法里去的,就是下面这个函数,_customMethods里面声明了很多需要追加在Object上的函数。 for (var method in _customMethods) { if (_customMethods.hasOwnProperty(method)) { Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false}) } } 1. require 调用 require('UIAlertView') 后,就可以直接使用UIAlertView这个变量去调用相应的类方法了,require做的事很简单,就是在JS全局作用域上创建一个同名变量,变量指向一个对象,对象属性 __clsName 保存类名,同时表明这个对象是一个 OC Class。 var _require = function(clsName) { if (!global[clsName]) { global[clsName] = { __clsName: clsName } } return global[clsName] } 这样我们在接下来调用UIAlertView.__c()方法的时候系统就不会报错了,因为它已经是JS中一个全局的Object对象了。 { __clsName: "UIAlertView" } 2.defineClass 接下来我们就要执行defineClass函数了 global.defineClass = function(declaration, properties, instMethods, clsMethods) defineClass函数可接受四个参数:字符串:”需要替换或者新增的类名:继承的父类名 <实现的协议1,实现的协议2>”[属性]{实例方法}{类方法} 当我调用这个函数以后主要是做三件事情: 执行_formatDefineMethods方法,主要目的是修改传入的function函数的的格式,以及在原来实现上追加了从OC回调回来的参数解析。 然后执行_OC_defineClass方法,也就是调用OC的方法,解析传入类的属性,实例方法,类方法,里面会调用overrideMethod方法,进行method swizzing操作,也就是方法的重定向。 最后执行_setupJSMethod方法,在js中通过_ocCls记录类实例方法,类方法。 关于_formatDefineMethods _formatDefineMethods方法接收的参数是一个方法列表js对象,加一个新的js空对象 var _formatDefineMethods = function(methods, newMethods, realClsName) { for (var methodName in methods) { if (!(methods[methodName] instanceof Function)) return; (function(){ var originMethod = methods[methodName] newMethods[methodName] = [originMethod.length, function() { try { // 通过OC回调回来执行,获取参数 var args = _formatOCToJS(Array.prototype.slice.call(arguments)) var lastSelf = global.self global.self = args[0] if (global.self) global.self.__realClsName = realClsName // 删除前两个参数:在OC中进行消息转发的时候,前两个参数是self和selector, // 我们在实际调用js的具体实现的时候,需要把这两个参数删除。 args.splice(0,1) var ret = originMethod.apply(originMethod, args) global.self = lastSelf return ret } catch(e) { _OC_catch(e.message, e.stack) } }] })() } } 可以发现,具体实现是遍历方法列表对象的属性(方法名),然后往js空对象中添加相同的属性,它的值对应的是一个数组,数组的第一个值是方法名对应实现函数的参数个数,第二个值是一个函数(也就是方法的具体实现)。_formatDefineMethods作用,简单的说,它把defineClass中传递过来的js对象进行了修改: 原来的形式是: { testFuncationOne:function(){...} } 修改之后是: { testFuncationOne: [argCount, function (){...新的实现}] } 传递参数个数的目的是,runtime在修复类的时候,无法直接解析原始的js实现函数,那么就不知道参数的个数,特别是在创建新的方法的时候,需要根据参数个数生成方法签名,也就是还原方法名字,所以只能在js端拿到js函数的参数个数,传递到OC端。 // js 方法 initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles // oc 方法 initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles: 关于_OC_defineClass 使用NSScanner分离classDeclaration,分离成三部分 类名 : className 父类名 : superClassName 实现的协议名 : protocalNames 使用NSClassFromString(className)获得该Class对象。 若该Class对象为nil,则说明JS端要添加一个新的类,使用objc_allocateClassPair与objc_registerClassPair注册一个新的类。 若该Class对象不为nil,则说明JS端要替换一个原本已存在的类 根据从JS端传递来的实例方法与类方法参数,为这个类对象添加/替换实例方法与类方法 添加实例方法时,直接使用上一步得到class对象; 添加类方法时需要调用objc_getMetaClass方法获得元类。 如果要替换的类已经定义了该方法,则直接对该方法替换和实现消息转发。 否则根据以下两种情况进行判断 遍历protocalNames,通过objc_getProtocol方法获得协议对象,再使用protocol_copyMethodDescriptionList来获得协议中方法的type和name。匹配JS中传入的selectorName,获得typeDescription字符串,对该协议方法的实现消息转发。 若不是上述两种情况,则js端请求添加一个新的方法。构造一个typeDescription为”@@:\@*”(返回类型为id,参数值根据JS定义的参数个数来决定。新增方法的返回类型和参数类型只能为id类型,因为在JS端只能定义对象)的IMP。将这个IMP添加到类中。 为该类添加setProp:forKey和getProp:方法,使用objc_getAssociatedObject与objc_setAssociatedObject让JS脚本拥有设置property的能力 返回{className:cls}回JS脚本。 不过其中还包括一个overrideMethod方法,不管是替换方法还是新增方法,都是使用overrideMethod方法。它的目的主要在于进行method swizzing操作,也就是方法的重定向。我们把所有的消息全部都转发到ForwardInvocation函数里去执行(不知道的同学请自行补消息转发机制),这样做的目的在于,我们可以在NSInvocation中获取到所有的参数,这样就可以实现一个通用的IMP,任意方法任意参数都可以通过这个IMP中转,拿到方法的所有参数回调JS的实现。于是overrideMethod其实就是做了如下这件事情: 具体实现,以替换 UIViewController 的 -viewWillAppear: 方法为例: 把UIViewController的-viewWillAppear:方法通过class_replaceMethod()接口指向_objc_msgForward这是一个全局 IMP,OC 调用方法不存在时都会转发到这个IMP上,这里直接把方法替换成这个IMP,这样调用这个方法时就会走到-forwardInvocation:。 为UIViewController添加-ORIGviewWillAppear:和-_JPviewWillAppear: 两个方法,前者指向原来的IMP实现,后者是新的实现,稍后会在这个实现里回调JS函数。 改写UIViewController的-forwardInvocation: 方法为自定义实现。一旦OC里调用 UIViewController 的-viewWillAppear:方法,经过上面的处理会把这个调用转发到-forwardInvocation:,这时已经组装好了一个NSInvocation,包含了这个调用的参数。在这里把参数从 NSInvocation反解出来,带着参数调用上述新增加的方法 -JPviewWillAppear:,在这个新方法里取到参数传给JS,调用JS的实现函数。整个调用过程就结束了,整个过程图示如下: 1.png 关于_setupJSMethod if (properties) { properties.forEach(function(o){ _ocCls[className]['props'][o] = 1 _ocCls[className]['props']['set' + o.substr(0,1).toUpperCase() + o.substr(1)] = 1 }) } var _setupJSMethod = function(className, methods, isInst, realClsName) { for (var name in methods) { var key = isInst ? 'instMethods': 'clsMethods', func = methods[name] _ocCls[className][key][name] = _wrapLocalMethod(name, func, realClsName) } } 是最后的一步是把之前所有的方法以及属性放入 _ocCls中保存起来,最后再调用require把类保存到全局变量中。 到这一步为止,我们的JS脚本中的所有对象已经,通过runtime替换到我们的程序中去了,也就是说,剩下的就是如何在我们出触发函数以后,能正确的去执行JS中函数的内容。 3. 对象持有/转换 下面引用作者的一段话: require('UIView')这句话在JS全局作用域生成了UIView这个对象,它有个属性叫 __isCls,表示这代表一个OC类。调用UIView这个对象的alloc()方法,会去到_c()函数,在这个函数里判断到调用者_isCls 属性,知道它是代表OC类,把方法名和类名传递给OC完成调用。调用类方法过程是这样,那实例方法呢?UIView.alloc()会返回一个UIView实例对象给JS,这个OC实例对象在JS是怎样表示的?怎样可以在 JS 拿到这个实例对象后可以直接调用它的实例方法UIView.alloc().init()? 对于一个自定义id对象,JavaScriptCore 会把这个自定义对象的指针传给JS,这个对象在JS无法使用,但在回传给OC时,OC可以找到这个对象。对于这个对象生命周期的管理,按我的理解如果JS有变量引用时,这个OC对象引用计数就加1,JS变量的引用释放了就减1,如果OC上没别的持有者,这个OC对象的生命周期就跟着 JS走了,会在JS进行垃圾回收时释放。传回给JS的变量是这个OC对象的指针,这个指针也可以重新传回OC,要在JS调用这个对象的某个实例方法,根据第2点JS接口的描述,只需在_c()函数里把这个对象指针以及它要调用的方法名传回给OC就行了,现在问题只剩下:怎样在_c()函数里判断调用者是一个OC对象指针?目前没找到方法判断一个JS对象是否表示 OC 指针,这里的解决方法是在OC把对象返回给JS之前,先把它包装成一个NSDictionary: static NSDictionary *_wrapObj(id obj) { return @{@"__obj": obj}; } 让 OC 对象作为这个 NSDictionary 的一个值,这样在 JS 里这个对象就变成: {__obj: [OC Object 对象指针]} 这样就可以通过判断对象是否有_obj属性得知这个对象是否表示 OC 对象指针,在_c函数里若判断到调用者有_obj属性,取出这个属性,跟调用的实例方法一起传回给OC,就完成了实例方法的调用。 但是: JS无法调用 NSMutableArray / NSMutableDictionary / NSMutableString 的方法去修改这些对象的数据,因为这三者都在从OC返回到JS时 JavaScriptCore 把它们转成了JS的Array/Object/String,在返回的时候就脱离了跟原对象的联系,这个转换在JavaScriptCore里是强制进行的,无法选择。 若想要在对象返回JS后,回到OC还能调用这个对象的方法,就要阻止JavaScriptCore的转换,唯一的方法就是不直接返回这个对象,而是对这个对象进行封装,JPBoxing 就是做这个事情的。 把NSMutableArray/NSMutableDictionary/NSMutableString对象作为JPBoxing的成员保存在JPBoxing实例对象上返回给JS,JS拿到的是JPBoxing对象的指针,再传回给OC时就可以通过对象成员取到原来的NSMutableArray/NSMutableDictionary/NSMutableString对象,类似于装箱/拆箱操作,这样就避免了这些对象被JavaScriptCore转换。 实际上只有可变的NSMutableArray/NSMutableDictionary/NSMutableString这三个类有必要调用它的方法去修改对象里的数据,不可变的NSArray/NSDictionary/NSString是没必要这样做的,直接转为JS对应的类型使用起来会更方便,但为了规则简单,JSPatch让NSArray/NSDictionary/NSString也同样以封装的方式返回,避免在调用OC方法返回对象时还需要关心它返回的是可变还是不可变对象。最后整个规则还是挺清晰:NSArray/NSDictionary/NSString 及其子类与其他 NSObject 对象的行为一样,在JS上拿到的都只是其对象指针,可以调用它们的OC方法,若要把这三种对象转为对应的JS类型,使用额外的.toJS()的接口去转换。 对于参数和返回值是C指针和 Class 类型的支持同样是用 JPBoxing 封装的方式,把指针和Class作为成员保存在JPBoxing对象上返回给JS,传回OC时再解出来拿到原来的指针和Class,这样JSPatch就支持所有数据类型OC<->JS的互传了。 4. 类型转换 还是引用作者的一段话: JS把要调用的类名/方法名/对象传给OC后,OC调用类/对象相应的方法是通过NSInvocation实现,要能顺利调用到方法并取得返回值,要做两件事: 取得要调用的 OC 方法各参数类型,把 JS 传来的对象转为要求的类型进行调用。 根据返回值类型取出返回值,包装为对象传回给 JS。 例如举例子的来讲view.setAlpha(0.5),JS传递给OC的是一个NSNumber,OC需要通过要调用OC方法的 NSMethodSignature得知这里参数要的是一个float类型值,于是把NSNumber转为float值再作为参数进行OC方法调用。这里主要处理了int/float/bool等数值类型,并对CGRect/CGRange等类型进行了特殊转换处理。 5. callSelector callSelector这个就是我们最后执行函数了!但是在执行这个函数之前,前面还有不少东西。 关于 _c函数 __c: function(methodName) { var slf = this if (slf instanceof Boolean) { return function() { return false } } if (slf[methodName]) { return slf[methodName].bind(slf); } if (!slf.__obj && !slf.__clsName) { throw new Error(slf + '.' + methodName + ' is undefined') } if (slf.__isSuper && slf.__clsName) { slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName); } var clsName = slf.__clsName if (clsName && _ocCls[clsName]) { var methodType = slf.__obj ? 'instMethods': 'clsMethods' if (_ocCls[clsName][methodType][methodName]) { slf.__isSuper = 0; return _ocCls[clsName][methodType][methodName].bind(slf) } if (slf.__obj && _ocCls[clsName]['props'][methodName]) { if (!slf.__ocProps) { var props = _OC_getCustomProps(slf.__obj) if (!props) { props = {} _OC_setCustomProps(slf.__obj, props) } slf.__ocProps = props; } var c = methodName.charCodeAt(3); if (methodName.length > 3 && methodName.substr(0,3) == 'set' && c >= 65 && c <= 90) { return function(val) { var propName = methodName[3].toLowerCase() + methodName.substr(4) slf.__ocProps[propName] = val } } else { return function(){ return slf.__ocProps[methodName] } } } } return function(){ var args = Array.prototype.slice.call(arguments) return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper) } } 其实_c函数就是一个消息转发中心,它根据传入的参数,这里可以分为两种类型来讲述: 对于实例方法和类方法,最后会调用_methodFunc方法 对于自定义的属性,set和get操作。 对于自定义的属性,其实它并不会将这些属性真正添加到OC中的对象里去,它只会添加一个_ocProps对象,然后在JS中,通过_ocProps对象来保存我们所有定义的属性,要获取值的只要从这个属性里通过name获取就可以了。 对于_methodFunc方法,其实就是将OC方法的名字还原,带上参数,然后转发给类方法或者实例方法处理。 var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) { var selectorName = methodName if (!isPerformSelector) { methodName = methodName.replace(/__/g, "-") selectorName = methodName.replace(/_/g, ":").replace(/-/g, "_") var marchArr = selectorName.match(/:/g) var numOfArgs = marchArr ? marchArr.length : 0 if (args.length > numOfArgs) { selectorName += ":" } } var ret = instance ? _OC_callI(instance, selectorName, args, isSuper): _OC_callC(clsName, selectorName, args) return _formatOCToJS(ret) } 对于callSelector方法来讲: 初始化 将JS封装的instance对象进行拆装,得到OC的对象; 根据类名与selectorName获得对应的类对象与selector; 通过类对象与selector构造对应的NSMethodSignature签名,再根据签名构造NSInvocation对象,并为invocation对象设置target与Selector 根据方法签名,获悉方法每个参数的实际类型,将JS传递过来的参数进行对应的转换(比如说参数的实际类型为int类型,但是JS只能传递NSNumber对象,需要通过[[jsObj toNumber] intValue]进行转换)。转换后使用setArgument方法为NSInvocation对象设置参数。 执行invoke方法。 通过getReturnValue方法获取到返回值。 根据返回值类型,封装成JS中对应的对象(因为JS并不识别OC对象,所以返回值为OC对象的话需封装成{className:className, obj:obj})返回给JS端。 总结 好,到现在为止,我们所有的流程就已经走完了,我们的js文件也已经生效了。当然,我所说JSPatch原理只是基础的一部份原理,可以使我们的基本流程可以实现,还有一些复杂的操作功能,还需要再深入的学习,才可以掌握,JSPatch对于学习Runtime也是一个不错的例子,就像Aspects一样,大家可以去好好研究一下。 文/北辰明(简书作者) 原文链接:http://www.jianshu.com/p/66dad614b905 http://www.jianshu.com/p/66dad614b905?utm_campaign=hugo&utm_medium=reader_share&utm_content=note
runtime的资料网上有很多了,部分有些晦涩难懂,我通过自己的学习方法总结一遍,主要讲一 些常用的方法功能,以实用为主,我觉得用到印象才是最深刻的。另外runtime的知识还有很多什么是runtime?runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h> 或<objc/message.h>),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。比如我们创建了一个对象 [[NSObject alloc]init],最终被转换为几万行代码。开这个帖子记录一下看到的一些比较好的runtime的文章。1、详解Runtime运行时机制http://www.code4app.com/blog-721976-204.html(亮点:本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机)2、OC实用的runtime总结http://www.code4app.com/home.php?mod=space&uid=721976&do=blog&quickforward=1&id=205 (亮点:通过自己的学习方法总结一遍,主要讲一些常用的方法功能,以实用为主)3、DEMO 按钮点击,利用runtime hook 实现iOS防止按钮连续响应点击http://www.code4app.com/forum.php?mod=viewthread&tid=7271&extra=page%3D1%26filter%3Dsortid%26sortid%3D1(利用runtime hook 实现iOS防止按钮连续响应点击 可以设置时间间隔,拖进工程立即生效)4、DEMO runtime 详解http://www.code4app.com/forum.php?mod=viewthread&tid=8241&extra=page%3D1%26filter%3Dsortid%26sortid%3D15、DEMO iOS Runtime 实践http://www.code4app.com/forum.php?mod=viewthread&tid=7638&extra=page%3D1%26filter%3Dsortid%26sortid%3D1(亮点:通过DEMO方式实践iOS中的黑魔法runtime)6、学习 runtime的简单项目http://www.code4app.com/forum.php?mod=viewthread&tid=7201&extra=page%3D1%26filter%3Dsortid%26sortid%3D17、根据规则跳转到指定的界面(runtime实用篇一)http://www.code4app.com/forum.php?mod=viewthread&tid=10131&extra=page%3D1%26filter%3Dsortid%26sortid%3D1 8、Objective-C 中的runtimehttp://www.code4app.com/home.php?mod=space&uid=800778&do=blog&quickforward=1&id=282 Objective-C中的Runtime http://www.jianshu.com/p/3e050ec3b759 Objective-C Runtime http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
1、先来几个常用的: [csharp] view plain copy // 是否高清屏 #define isRetina ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO) // 是否模拟器 #define isSimulator (NSNotFound != [[[UIDevice currentDevice] model] rangeOfString:@"Simulator"].location) // 是否iPad #define isPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) // 是否iPad #define someThing (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)? ipad: iphone 2、基本的使用: [html] view plain copy //定义π值 3.1415926 #define PI 3.1415926 //则在程序用可以如下使用 double i=2*PI*3; //效果相当于 double i=2*3.1415926*3; //预处理命令可以定义任何符合格式的形式,例如判断年份是否闰年 #define IS_LEAP_YEAR year%4==0&&year%100!=0||year%400==0 //使用时则可以直接 if(IS_LEAP_YEAR) //或者可以定义一个参数 #define IS_LEAP_YEAR(y) y%4==0&&y%100!=0||y%400==0 //使用时则可以直接 int ys=2012; if(IS_LEAP_YEAR(ys)) //通常预处理程序定义在一行 如果好分行 比如说太长需要换行 需要使用“/”符号 表示还有下一行,多行分列也是如此,例: #Define IS_LEAP_YEAR year%4==0&&year%100!=0/ ||year%400==0 //宏定义参数后边放一个# 那么在调用该宏时,预处理程序将根据宏参数创建C风格的常量字符串 例: #define STR(x) # x //将会使得 随后调用的 NSLOG(STR(Programming in Objective-c./n)); //显示结果为 Programming in Objective-c./n 3、关于#与##的操作符: <1>.宏定义中字符串化操作符#: #的功能是将其后面的宏参数进行字符串化操作,意思就是对它所应用的宏变量通过替换后在其左右各加上一个双引号。例如 [csharp] view plain copy #define WARN_IF(EXPR)\ do {\ if (EXPR)\ fprintf(stderr, "Warning: " #EXPR "\n");\ } while(0) 上面代码中的反斜线\主要用来转译换行符,即屏蔽换行符。 那么如下的代码调用: WARN_IF(divider == 0); 将被解析为: do {\ if (divider == 0)\ fprintf(stderr, "Warning: " "divider == 0" "\n");\ } while(0); 注意能够字符串化操作的必须是宏参数,不是随随便便的某个子串(token)都行的。 <2>.宏定义中的连接符##: 连接符##用来将两个token连接为一个token,但它不可以位于第一个token之前or最后一个token之后。注意这里连接的对象只要是token就行,而不一定是宏参数,但是##又必须位于宏定义中才有效,因其为编译期概念(比较绕)。 [html] view plain copy #define LINK_MULTIPLE(a, b, c, d) a##_##b##_##c##_##d typedef struct _record_type LINK_MULTIPLE(name, company, position, salary); /* * 上面的代码将被替换为 * typedef struct _record_type name_company_position_salary; */ 又如下面的例子: #define PARSER(N) printf("token" #N " = %d\n", token##N) int token64 = 64; 如下调用宏: PARSER(64); 将被解析为: printf("token" "64" " = %d\n", token64); 在obj-c中,如果我有如下定义: #define _X(A, B) (A#B) #define _XX(A, B) _X([NSString stringWithFormat:@"%@_c", A], B) gcc将报错! 正确的写法为: #define _XX(A, B) _X(([NSString stringWithFormat:@"%@_c", A]), B) 4、再来个宏定义 object-c 单例 [csharp] view plain copy #define GTMOBJECT_SINGLETON_BOILERPLATE(_object_name_, _shared_obj_name_) static _object_name_ *z##_shared_obj_name_ = nil; + (_object_name_ *)_shared_obj_name_ { @synchronized(self) { if (z##_shared_obj_name_ == nil) { /* Note that ‘self’ may not be the same as _object_name_ */ /* first assignment done in allocWithZone but we must reassign in case init fails */ z##_shared_obj_name_ = [[self alloc] init]; _GTMDevAssert((z##_shared_obj_name_ != nil), @”didn’t catch singleton allocation”); } } return z##_shared_obj_name_; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (z##_shared_obj_name_ == nil) { z##_shared_obj_name_ = [super allocWithZone:zone]; return z##_shared_obj_name_; } } /* We can’t return the shared instance, because it’s been init’d */ _GTMDevAssert(NO, @”use the singleton API, not alloc+init”); return nil; } - (id)retain { return self; } - (NSUInteger)retainCount { return NSUIntegerMax; } - (void)release { } - (id)autorelease { return self; } - (id)copyWithZone:(NSZone *)zone { return self; } 5、条件编译: [csharp] view plain copy #if !defined(FCDebug) || FCDebug == 0 #define FCLOG(...) do {} while (0) #define FCLOGINFO(...) do {} while (0) #define FCLOGERROR(...) do {} while (0) #elif FCDebug == 1 #define FCLOG(...) NSLog(__VA_ARGS__) #define FCLOGERROR(...) NSLog(__VA_ARGS__) #define FCLOGINFO(...) do {} while (0) #elif FCDebug > 1 #define FCLOG(...) NSLog(__VA_ARGS__) #define FCLOGERROR(...) NSLog(__VA_ARGS__) #define FCLOGINFO(...) NSLog(__VA_ARGS__) #endif 6、参照C语言的预处理命令简介 : #define 定义一个预处理宏 #undef 取消宏的定义 #include 包含文件命令 #include_next 与#include相似, 但它有着特殊的用途 #if 编译预处理中的条件命令, 相当于C语法中的if语句 #ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句 #ifndef 与#ifdef相反, 判断某个宏是否未被定义 #elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if #else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else #endif #if, #ifdef, #ifndef这些条件命令的结束标志. defined 与#if, #elif配合使用, 判断某个宏是否被定义 #line 标志该语句所在的行号 # 将宏参数替代为以参数值为内容的字符窜常量 ## 将两个相邻的标记(token)连接为一个单独的标记 #pragma 说明编译器信息#warning 显示编译警告信息 #error 显示编译错误信息 C中的预编译宏定义 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的"宏(macro)"进行处理. C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了. 编译预处理往往在后台运行. 在有的C编译器中, 这些过程统统由一个单独的程序来完成, 编译的不同阶段实现这些不同的功能. 可以指定相应的命令选项来执行这些功能. 有的C编译器使用分别的程序来完成这些步骤. 可单独调用这些程序来完成. 在gcc中, 进行编译预处理的程序被称为CPP, 它的可执行文件名为cpp. 编译预处理命令的语法与C语言的语法是完全独立的. 比如: 你可以将一个宏扩展为与C语法格格不入的内容, 但该内容与后面的语句结合在一个若能生成合法的C语句, 也是可以正确编译的.(一) 预处理命令简介预处理命令由#(hash字符)开头, 它独占一行, #之前只能是空白符. 以#开头的语句就是预处理命令, 不以#开头的语句为C中的代码行. 常用的预处理命令如下:#define 定义一个预处理宏#undef 取消宏的定义#include 包含文件命令#include_next 与#include相似, 但它有着特殊的用途#if 编译预处理中的条件命令, 相当于C语法中的if语句#ifdef 判断某个宏是否被定义, 若已定义, 执行随后的语句#ifndef 与#ifdef相反, 判断某个宏是否未被定义#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else#endif #if, #ifdef, #ifndef这些条件命令的结束标志.defined 与#if, #elif配合使用, 判断某个宏是否被定义#line 标志该语句所在的行号# 将宏参数替代为以参数值为内容的字符窜常量## 将两个相邻的标记(token)连接为一个单独的标记#pragma 说明编译器信息#warning 显示编译警告信息#error 显示编译错误信息(二) 预处理的文法预处理并不分析整个源代码文件, 它只是将源代码分割成一些标记(token), 识别语句中哪些是C语句, 哪些是预处理语句. 预处理器能够识别C标记, 文件名, 空白符, 文件结尾标志.预处理语句格式: #command name(...) token(s)1, command预处理命令的名称, 它之前以#开头, #之后紧随预处理命令, 标准C允许#两边可以有空白符, 但比较老的编译器可能不允许这样. 若某行中只包含#(以及空白符), 那么在标准C中该行被理解为空白. 整个预处理语句之后只能有空白符或者注释, 不能有其它内容.2, name代表宏名称, 它可带参数. 参数可以是可变参数列表(C99).3, 语句中可以利用"\"来换行.e.g.# define ONE 1 /* ONE == 1 */等价于: #define ONE 1#define err(flag, msg) if(flag) \ printf(msg)等价于: #define err(flag, msg) if(flag) printf(msg)(三) 预处理命令详述1, #define#define命令定义一个宏:#define MACRO_NAME(args) tokens(opt)之后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 而后面的标记也是可选的.对象宏不带参数的宏被称为"对象宏(objectlike macro)"#define经常用来定义常量, 此时的宏名称一般为大写的字符串. 这样利于修改这些常量.e.g.#define MAX 100int a[MAX];#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endif#define __FILE_H__ 中的宏就不带任何参数, 也不扩展为任何标记. 这经常用于包含头文件.要调用该宏, 只需在代码中指定宏名称, 该宏将被替代为它被定义的内容.函数宏带参数的宏也被称为"函数宏". 利用宏可以提高代码的运行效率: 子程序的调用需要压栈出栈, 这一过程如果过于频繁会耗费掉大量的CPU运算资源. 所以一些代码量小但运行频繁的代码如果采用带参数宏来实现会提高代码的运行效率.函数宏的参数是固定的情况函数宏的定义采用这样的方式: #define name( args ) tokens其中的args和tokens都是可选的. 它和对象宏定义上的区别在于宏名称之后不带括号.注意, name之后的左括号(必须紧跟name, 之间不能有空格, 否则这就定义了一个对象宏, 它将被替换为 以(开始的字符串. 但在调用函数宏时, name与(之间可以有空格.e.g.#define mul(x,y) ((x)*(y))注意, 函数宏之后的参数要用括号括起来, 看看这个例子:e.g.#define mul(x,y) x*y"mul(1, 2+2);" 将被扩展为: 1*2 + 2同样, 整个标记串也应该用括号引用起来:e.g.#define mul(x,y) (x)*(y)sizeof mul(1,2.0) 将被扩展为 sizeof 1 * 2.0调用函数宏时候, 传递给它的参数可以是函数的返回值, 也可以是任何有意义的语句:e.g.mul (f(a,b), g(c,d));e.g.#define insert(stmt) stmtinsert ( a=1; b=2;) 相当于在代码中加入 a=1; b=2 .insert ( a=1, b=2;) 就有问题了: 预处理器会提示出错: 函数宏的参数个数不匹配. 预处理器把","视为参数间的分隔符. insert ((a=1, b=2;)) 可解决上述问题.在定义和调用函数宏时候, 要注意一些问题:1, 我们经常用{}来引用函数宏被定义的内容, 这就要注意调用这个函数宏时的";"问题.example_3.7:#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}如果这样调用它: "swap(1,2);" 将被扩展为: { unsigned long _temp=1; 1=2; 2=_tmp}; 明显后面的;是多余的, 我们应该这样调用: swap(1,2)虽然这样的调用是正确的, 但它和C语法相悖, 可采用下面的方法来处理被{}括起来的内容:#define swap(x,y) \ do { unsigned long _temp=x; x=y; y=_tmp} while (0)swap(1,2); 将被替换为:do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);在Linux内核源代码中对这种do-while(0)语句有这广泛的应用.2, 有的函数宏是无法用do-while(0)来实现的, 所以在调用时不能带上";", 最好在调用后添加注释说明.eg_3.8:#define incr(v, low, high) \ for ((v) = (low),; (v) <= (high); (v)++)只能以这样的形式被调用: incr(a, 1, 10) /* increase a form 1 to 10 */函数宏中的参数包括可变参数列表的情况C99标准中新增了可变参数列表的内容. 不光是函数, 函数宏中也可以使用可变参数列表.#define name(args, ...) tokens#define name(...) tokens"..."代表可变参数列表, 如果它不是仅有的参数, 那么它只能出现在参数列表的最后. 调用这样的函数宏时, 传递给它的参数个数要不少于参数列表中参数的个数(多余的参数被丢弃). 通过__VA_ARGS__来替换函数宏中的可变参数列表. 注意__VA_ARGS__只能用于函数宏中参数中包含有"..."的情况.e.g.#ifdef DEBUG#define my_printf(...) fprintf(stderr, __VA_ARGS__)#else#define my_printf(...) printf(__VA_ARGS__)#endiftokens中的__VA_ARGS__被替换为函数宏定义中的"..."可变参数列表. 注意在使用#define时候的一些常见错误:#define MAX = 100#define MAX 100;=, ; 的使用要值得注意. 再就是调用函数宏是要注意, 不要多给出";". 注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍. 关于定义宏的另外一些问题(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...e.g.#define NULL 0#define NULL /* null pointer */ 0上面的重定义是相同的, 但下面的重定义不同:#define fun(x) x+1#define fun(x) x + 1 或: #define fun(y) y+1如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.(2) 在gcc中, 可在命令行中指定对象宏的定义:e.g.$ gcc -Wall -DMAX=100 -o tmp tmp.c相当于在tmp.c中添加" #define MAX 100".那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?---若-DMAX=1, 则正确编译.---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1(3) #define所定义的宏的作用域宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别e.g.#define ONE 1sum = ONE + TWO /* sum = 1 + TWO */#define TWO 2sum = ONE + TWO /* sum = 1 + 2 */ #undef ONEsum = ONE + TWO /* sum = ONE + 2 */char c[] = "TWO" /* c[] = "TWO", NOT "2"! */(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.e.g.# define ONE NUMBER_1# define NUMBER_1 1int a = ONE /* a = 1 */2, #undef#undef用来取消宏定义, 它与#define对立:#undef name如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误.当一个宏定义被取消后, 可以再度定义它. 3, #if, #elif, #else, #endif#if, #elif, #else, #endif用于条件编译:#if 常量表达式1 语句...#elif 常量表达式2 语句...#elif 常量表达式3 语句......#else 语句...#endif#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.e.g.#if 0{ 一大段代码;}#endif常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.#if MACRO_NON_DEFINED == #if 0在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.4, #ifdef, #ifndef, defined.#ifdef, #ifndef, defined用来测试某个宏是否被定义#ifdef name 或 #ifndef name它们经常用于避免头文件的重复引用:#ifndef __FILE_H__#define __FILE_H__#include "file.h"#endifdefined(name): 若宏被定义,则返回1, 否则返回0.它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:#if defined(VAX) && defined(UNIX) && !defined(DEBUG) 和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.5, #include , #include_next#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.#include "headfile"#include <headfile>#include 预处理标记前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include <headfile>包包含的"系统头文件"可能是另外的文件. 但这不值得被注意. 感兴趣的话可以查看宏扩展后到底引入了哪些系统头文件.关于#include "headfile"和#include <headfile>的区别以及如何在gcc中包含头文件的详细信息, 参考本blog的GCC笔记.相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.可参考cpp手册进一步了解#include_next6, 预定义宏标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:__LINE__ 当前语句所在的行号, 以10进制整数标注.__FILE__ 当前源文件的文件名, 以字符串常量标注.__DATE__ 程序被编译的日期, 以"Mmm dd yyyy"格式的字符串标注.__TIME__ 程序被编译的时间, 以"hh:mm:ss"格式的字符串标注, 该时间由asctime返回.__STDC__ 如果当前编译器符合ISO标准, 那么该宏的值为1__STDC_VERSION__ 如果当前编译器符合C89, 那么它被定义为199409L, 如果符合C99, 那么被定义为199901L. 我用gcc, 如果不指定-std=c99, 其他情况都给出__STDC_VERSION__未定义的错误信息, 咋回事呢?__STDC_HOSTED__ 如果当前系统是"本地系统(hosted)", 那么它被定义为1. 本地系统表示当前系统拥有完整的标准C库.gcc定义的预定义宏:__OPTMIZE__ 如果编译过程中使用了优化, 那么该宏被定义为1.__OPTMIZE_SIZE__ 同上, 但仅在优化是针对代码大小而非速度时才被定义为1.__VERSION__ 显示所用gcc的版本号.可参考"GCC the complete reference".要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null7, #line#line用来修改__LINE__和__FILE__. e.g. printf("line: %d, file: %s\n", __LINE__, __FILE__);#line 100 "haha" printf("line: %d, file: %s\n", __LINE__, __FILE__); printf("line: %d, file: %s\n", __LINE__, __FILE__);显示:line: 34, file: 1.cline: 100, file: hahaline: 101, file: haha 8, #pragma, _Pragma#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:#pragma GCC name token(s)#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.(1) #pragma GCC dependencydependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息. e.g.在demo.c中给出这样一句:#pragma GCC dependency "temp-file"然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息: warning: current file is older than temp-file如果当前文件比指定的文件新, 则不给出任何警告信息.还可以在在#pragma中给添加自定义的警告信息.e.g.#pragma GCC dependency "temp-file" "demo.c needs to be updated!" 1.c:27:38: warning: extra tokens at end of #pragma directive1.c:27:38: warning: current file is older than temp-file注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.(2) #pragma GCC poison token(s)若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.e.g.#pragma GCC poison scanfscanf("%d", &a); warning: extra tokens at end of #pragma directiveerror: attempt to use poisoned "scanf"注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.(3) #pragma GCC system_header从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明). (这条#pragma语句还没发现用什么大的用处)由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:e.g.#define PRAGMA_DEP #pragma GCC dependency "temp-file"由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")注意, ()中包含的""引用之前引该加上\转义字符.9, #, ###和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.e.g.#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b));注意: #只针对紧随其后的token有效!##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.e.g.#define TYPE(type, n) type n之后调用: TYPE(int, a) = 1;TYPE(long, b) = 1999;将被替换为:int a = 1;long b = 1999;(10) #warning, #error#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:#warning tokens#error tokense.g.#warning "some warning"注意, #error和#warning后的token要用""引用起来!(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.
iOS Anti-Debug 1 iOS Anti-Debug前言 移动平台攻防对抗技术的发展基本是沿着PC端发展轨迹在前进,从windows平台上的加壳到Android平台的APK加固,相信ipa的加固也已经不远了;windows平台下从ring3层到ring0层的反调试技术已经非常成熟,Android平台下的反调试技术已经出现了好几套不错的方案,今天来简单聊聊iOS下的反调试技术。 2 PT_DENY_ATTACH 谈到debug,首先会想到的一个系统调用是ptrace,它主要用于实现断点调试和系统调用跟踪。从man ptrace的结果发现一个有趣的参数——PT_DENY_ATTACH: 图 1 man ptrace PT_DENY_ATTACH是苹果增加的一个ptrace选项,用以防止gdb等调试器依附到某进程。用法如下: ptrace(PT_DENY_ATTACH, 0, 0, 0); 直接上demo: 图 2 ptrace not found 那么问题就来了,编译报错:'sys/ptrace.h' file not found。iPhone真实环境下,根本就没有抛出sys/ptrace.h。但是幸运的是,我们依然可以使用dlopen和dlsym拿到ptrace。从man dlopen和man dlsym可以发现,当dlopen的path参数传入NULL时,dlopen将返回一个相当于RTLD_DEFAULT的handle;当dlsym接收到一个RTLD_DEFAULT的handle参数时,dlsym将根据载入的顺序搜索除以dlopen(xxx, RTLD_LOCAL)方式载入的所有mach-o文件。 图 3 man dlopen 图 4 man dlsym 根据以上的信息,反调试方法基本可以完成了,demo主要代码如下: 图 5 Anti debug Demo 尝试gdb依附会得到一个Segmentation fault错误: 图 6 运行demo 图 7 gdb依附 针对这种ptrace的反反调试方法其实很简单,通过修改ptrace的参数或者内存补丁就可以搞定,具体不在此详述。 另外,在某亿级用户的APP里面发现了相同的反调试方法,伪代码如下: 图 8 某APP反调试伪代码 注意,在dlfcn.h中有如下定义: #define RTLD_GLOBAL 0x8 #define RTLD_NOW 0x2 3 sysctl 思路是通过sysctl查看信息进程里的标记,判断自己是否正在被调试。sysctl是用以查询内核状态的接口,并允许具备相应权限的进程设置内核状态。其定义如下: int sysctl(int *name, u_int namelen, void *old, size_t *oldlen, void *newp, size_t newlen); name参数是一个用以指定查询的信息数组; namelen用以指定name数组的元素个数; old是用以函数返回的缓冲区; oldlen用以指定oldp缓冲区长度; newp和newlen在设置时使用; 当进程被调试器依附时,kinfo_proc结构下有一个kp_proc结构域,kp_proc的p_flag的被调试标识将被设置,即会进行类似如下的设置: kinfo_proc. kp_proc. p_flag & P_TRACED 其中P_TRACED的定义如下: #define P_TRACED 0x00000800 /* Debugged process being traced */ 我们可以通过sysctl查询进程相应的kinfo_proc信息,查询函数的实现可以这样: 图 9 found debugger name参数的4个元素表示通过本进程pid查询本进程信息。在主函数中检索is_debugged的返回值,返回为真时,即进程正在被调试,退出进程。测试结果如下: 图 10 gdb依附demo 图 11 demo发现debugger并退出 针对sysctl的反反调试的思路其实很简单,只需要在函数返回时清除p_flag标识位即可,根据sysctl.h文件中的定义: #define CTL_KERN 1 #define KERN_PROC 14 #define KERN_PROC_PID 1 以及sysctl的第二个参数为4,对sysctl下条件断点,在sysctl返回后,根据反编译二进制文件找到kproc的首地址,接下来找到p_flag相对kproc首地址的偏移,最后修改对应内存地址的值就OK了。 4 iOS Anti-Debug小节 iOS平台下的Anti-Debug方法相对于Linux下的要少很多,例如fork一个子进程,ptrace父进程进行检测方式不再奏效。而且,要完全防止程序被调试或者被逆向,理论上来说是不可能的。 作者 轩夏
一、WebView漏洞持续高居榜首 阿里移动安全发布的2015第三季度移动安全报告【1】,对16个行业的top10 Android应用进行扫描,发现与Webview相关的漏洞依然高居榜首。 Webview相关问题早在2012年【2】就已经披露并广泛关注,但到为何到现在还是持续高居漏洞榜首?! 二、Webview是一个什么样的组件 WebView是Android中一个非常实用的组件,它和Safai、Chrome一样都是基于Webkit网页渲染引擎,可以通过加载HTML数据的方式便捷地展现软件的界面。使用WebView开发软件有以下几个优点: 1、 可以打开远程URL页面,也可以加载本地HTML数据; 2、 可以无缝的在java和javascript之间进行交互操作; 3、高度的定制性,可根据开发者的需要进行多样性定制。 三、Webview相关漏洞有哪些? 1、 Webview addJavascriptInterface远程代码执行漏洞,漏洞编号CVE-2012-6636: Webview是Android系统中为Android App用来渲染网页的,为了便于JS互动展示,一般都会调用addJavascriptInterface接口。用例如下: mWebView=new WebView(this); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new JavaScriptInterface(), " injectedObj "); mWebView.loadUrl("www.test.com"); setContentView(mWebView); 由于Android API level 16以及之前的版本没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法,简单的说就是通过addJavascriptInterface给WebView加入一个JavaScript桥接接口,JavaScript通过调用这个接口可以直接操作本地的JAVA环境环境,达到命令执行的目的。上面的代码可通过如下方法来执行任意命令: <html> <body> <script> function execute(cmdArgs) { return injectedObj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); } var res = execute(["/system/bin/sh", "-c", "ls -al /mnt/sdcard/"]); document.write(getContents(res.getInputStream())); </script> </body> </html> 2、 Android Webkit内置多个组件同样存在该漏洞: 2014年同时发现在Android系统中Webkit中默认内置的searchBoxJavaBridge_,accessibility,accessibilityTraversal组件都同样能引起类似的远程代码执行漏洞。漏洞编号为CVE-2014-1939、CVE-2014-7224。 3、Webview明文存储密码漏洞: Webview明文存储密码漏洞是当使用Webview加载登陆页面的时候,如果不设置setSavePassword(false),就会弹出一个对话框询问是否保存密码,一旦用户选择了是,密码就会被明文保存在目录下的databases/webview.db 文件中,存在密码被泄漏的风险。 不过幸运的是在api18之后,该接口已经被google废除。但是在API 17之前,还是存在这个问题。 四、为何此类漏洞数量还高居不下? 出于安全考虑,为了防止Java层的函数被随便调用,Google在4.2版本之后,规定允许被调用的函数必须以@JavascriptInterface进行注解,所以如果某应用限定的最小API Level为17或者以上,就不会受该问题的影响。但是由于多种遗留问题造成漏洞数量还是高居首位: 1、 截止到2015年12月,Android 4.2以下版本的市场占有率还有18.3%左右,很多开发必须兼顾这些用户。 2、引起远程代码执行漏洞的根本原因发生在Android Webkit内部, 开发者无法从API层修复这个问题。只能利用Android API做一些补救, 比如通过限制Webview加载的url只能来自可信域。但都无法从本质上修复和规避这个安全风险。 3、 安全与产品需求的折中,Webview中使用JavaScript几乎是大部分开发中都必须要用到的特性。它直接决定了的某些重要功能能否实现,或是否能大幅度提升用户体验。当安全风险和产品功能相冲突时,产品功能的优先级往往都高于安全。 4、开发人员安全意识的薄弱。产品开发和产品安全虽然是孪生兄弟,但从产品角度看却属于不同分工。一个优秀的开发人员可能对安全风险感知很小或根本就缺乏重视。他们可能会为一个变量命名绞尽脑汁或对一个算法精雕细琢但却对代码安全完全忽略。 5、而Webview明文存储密码漏洞纯粹是开发者安全意识问题。许多开发者认为即使软件中存在有安全风险的代码但如果没有被外部利用的路径就是安全的。 从安全的角度出发代码安全不是静态的。有安全风险的代码存在即是有危险的。 如,开发者Alex开发了某个模块,使用了Webview,但是没有设置setSavePassword(false)。Alex认为自己代码中没有登陆页面,所以不会有问题。一段时间后开发者Bob接手了Alex的工作,因为需求变动Bob增加了一个登陆界面,但是没有检查Alex的代码,就会导致Webview明文存储密码漏洞。 五、修复建议 1、针对WebView远程代码执行漏洞修复建议: - 继承Webview控件 因为以上5个原因都是来自webview这个控件,那么整体上来说,可以继承系统控件Webview,然后将安全问题全部集中在这个继承类里面解决。比如我们不用的addJavascriptInterface可以重载并抛出异常,移除searchBoxJavaBridge_,accessibility,accessibilityTraversa操作也可以在继承类中进行统一处理。 - 针对API Level等于或高于17的Android系统 出于安全考虑,为了防止Java层的函数被随便调用,Google在4.2版本之后,规定允许被调用的函数必须以@JavascriptInterface进行注解,所以如果某应用依赖的最小API Level为17或者以上,就能自然的避免风险。按照Google官方文档[5]使用示例: class JsObject { @JavascriptInterface public String toString() { return "injectedObject"; } } webView.addJavascriptInterface(new JsObject(), "injectedObject"); webView.loadData("", "text/html", null); webView.loadUrl("javascript:alert(injectedObject.toString())"); 建议不要使用addJavascriptInterface接口,以免带来不必要的安全隐患, 如果一定要使用addJavascriptInterface接口,请遵循如下规则: a、如果使用HTTPS协议加载URL,应进行证书校验防止访问的页面被篡改挂马; b、如果使用HTTP协议加载URL,应进行白名单过滤、完整性校验等防止访问的页面被篡改; c、如果加载本地Html,应将html文件内置在APK中,以及进行对html页面完整性的校验。 d、针对searchBoxJavaBridge_ 接口,这是开发者特别容易忽视的一个问题。大部分的远程代码执行问题来自于此。建议开发者一定需要通过以下方式移除该Javascript接口: removeJavascriptInterface("searchBoxJavaBridge_") 而针对"accessibility" 和"accessibilityTraversal",建议开发者通过以下方式移除该JavaScript接口: removeJavascriptInterface("accessibility"); removeJavascriptInterface("accessibilityTraversal"); 2、 针对WebView明文存储密码漏洞修复建议: 开发者需要在使用webview的地方务必加上setSavePassword(false)。 六、参考: 1,http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.CbH5hR&id=87 2,http://50.56.33.56/blog/?p=314 作者:舟海 呆狐
深度学习在最近十来年特别火,几乎是带动AI浪潮的最大贡献者。互联网视频在最近几年也特别火,短视频、视频直播等各种新型UGC模式牢牢抓住了用户的消费心里,成为互联网吸金的又一利器。当这两个火碰在一起,会产生什么样的化学反应呢? 不说具体的技术,先上一张福利图,该图展示了机器对一个视频的认知效果。其总红色的字表示objects, 蓝色的字表示scenes,绿色的字表示activities。 图1 人工智能在视频上的应用主要一个课题是视频理解,努力解决“语义鸿沟”的问题,其中包括了: · 视频结构化分析:即是对视频进行帧、超帧、镜头、场景、故事等分割,从而在多个层次上进行处理和表达。 · 目标检测和跟踪:如车辆跟踪,多是应用在安防领域。 · 人物识别:识别出视频中出现的人物。 · 动作识别:Activity Recognition, 识别出视频中人物的动作。 · 情感语义分析:即观众在观赏某段视频时会产生什么样的心理体验。 短视频、直播视频中大部分承载的是人物+场景+动作+语音的内容信息,如图1所示,如何用有效的特征对其内容进行表达是进行该类视频理解的关键。传统的手工特征有一大堆,目前效果较好的是iDT(Improved Dense Trajectories) ,在这里就不加讨论了。深度学习对图像内容的表达能力十分不错,在视频的内容表达上也有相应的方法。下面介绍最近几年主流的几种技术方法。 1、基于单帧的识别方法 一种最直接的方法就是将视频进行截帧,然后基于图像粒度(单帧)的进行deep learninig 表达, 如图2所示,视频的某一帧通过网络获得一个识别结果。图2为一个典型的CNN网络,红色矩形是卷积层,绿色是归一化层,蓝色是池化层 ,黄色是全连接层。然而一张图相对整个视频是很小的一部分,特别当这帧图没有那么的具有区分度,或是一些和视频主题无关的图像,则会让分类器摸不着头脑。因此,学习视频时间域上的表达是提高视频识别的主要因素。当然,这在运动性强的视频上才有区分度,在较静止的视频上只能靠图像的特征了。 图2 2、基于CNN扩展网络的识别方法 它的总体思路是在CNN框架中寻找时间域上的某个模式来表达局部运动信息,从而获得总体识别性能的提升。图3是网络结构,它总共有三层,在第一层对10帧 (大概三分之一秒)图像序列进行MxNx3xT的卷积(其中 MxN是图像的分辨率,3是图像的3个颜色通道,T取4,是参与计算的帧数,从而形成在时间轴上4个响应),在第2、3层上进行T=2的时间卷积,那么在第3层包含了这10帧图片的所有的时空信息。该网络在不同时间上的同一层网络参数是共享参数的。 它的总体精度在相对单帧提高了2%左右,特别在运动丰富的视频,如摔角、爬杆等强运动视频类型中有较大幅度的提升,这从而也证明了特征中运动信息对识别是有贡献的。在实现时,这个网络架构可以加入多分辨的处理方法,可以提高速度。 图3 3、双路CNN的识别方法 这个其实就是两个独立的神经网络了,最后再把两个模型的结果平均一下。上面一个就是普通的单帧的CNN,而且文章当中提到了,这个CNN是在ImageNet的数据上pre-train,然后在视频数据上对最后一层进行调参。下面的一个CNN网络,就是把连续几帧的光流叠起来作为CNN的输入。 另外,它利用multi-task learning来克服数据量不足的问题。其实就是CNN的最后一层连到多个softmax的层上,对应不同的数据集,这样就可以在多个数据集上进行multi-task learning。网络结构如图4所示。 图4 4、基于LSTM的识别方法 它的基本思想是用LSTM对帧的CNN最后一层的激活在时间轴上进行整合。 这里,它没有用CNN全连接层后的最后特征进行融合,是因为全连接层后的高层特征进行池化已经丢失了空间特征在时间轴上的信息。相对于方法2,一方面,它可以对CNN特征进行更长时间的融合,不对处理的帧数加以上限,从而能对更长时长的视频进行表达;另一方面,方法2没有考虑同一次进网络的帧的前后顺序,而本网络通过LSTM引入的记忆单元,可以有效地表达帧的先后顺序。网络结构如图5所示。 图5 图5中红色是卷积网络,灰色是LSTM单元,黄色是softmax分类器。LSTM把每个连续帧的CNN最后一层卷积特征作为输入,从左向右推进时间,从下到上通过5层LSTM,最上的softmax层会每个时间点给出分类结果。同样,该网络在不同时间上的同一层网络参数是共享参数的。在训练时,视频的分类结果在每帧都进行BP(back Propagation),而不是每个clip进行BP。在BP时,后来的帧的梯度的权重会增大,因为在越往后,LSTM的内部状态会含有更多的信息。 在实现时,这个网络架构可以加入光流特征,可以让处理过程容忍对帧进行采样,因为如每秒一帧的采样已经丢失了帧间所隐含的运动信息,光流可以作为补偿。 5、3维卷积核(3D CNN)法 3D CNN 应用于一个视频帧序列图像集合,并不是简单地把图像集合作为多通道来看待输出多个图像(这种方式在卷积和池化后就丢失了时间域的信息,如图6上), 而是让卷积核扩展到时域,卷积在空域和时域同时进行,输出仍然是有机的图像集合(如图6下)。 图6 实现时,将视频分成多个包含16帧的片段作为网络的输入(维数为3 × 16 × 128 × 171)。池化层的卷积核的尺寸是d x k x k, 第一个池化层d=1,是为了保证时间域的信息不要过早地被融合,接下来的池化层的d=2。有所卷积层的卷积核大小为3x3x3,相对其他尺寸的卷积核,达到了精度最优,计算性能最佳。 网络结构如图7所示。这个是学习长度为16帧(采样后)视频片段的基础网络结构。对于一个完整的视频,会被分割成互相覆盖8帧的多个16帧的片段,分别提取他们的fc6特征,然后进行一个简单平均获得一个4096维的向量作为整个视频的特征。 图7 通过可视化最后一个卷积层对一个连续帧序列的特征表达,可以发现,在特征开始着重表达了画面的信息,在特征的后面着重表达的是运动信息,即在运动处有相对显著的特征。如图8。 图8 和单帧图特征在视频测试集上进行对比,3D CNN有更强的区分度,如图9。 图9 6、阿里聚安全内容安全(阿里绿网) 阿里聚安全内容安全(阿里绿网)基于深度学习技术及阿里巴巴多年的海量数据支撑, 提供多样化的内容识别服务,能有效帮助用户降低违规风险。其产品包括:ECS站点检测服务、OSS图片鉴黄服务、内容检测API服务。针对多媒体内容中的违规视频内容,绿网致力于提供一整套内容安全的垂直视频解决方案。以下是一些诸如图像识别,视频识别(人物动作识别)公开的训练、评测数据集。 · UCF-101 一共13320个视频, 共101个类别。 · HMDB51 一共7000个视频片段,共51个类别。 · activity-net 200类,10,024个训练视频,4,926个交叉验证视频,5,044 个测试视频。 · 1M sport 1.2 million个体育视频,有487个已标记的类,每类有1000到3000个视频。 7、参考资料 [1] Large-scale Video Classification with Convolutional Neural Networks [2] Two-stream convolutional networks for action recognition in videos [3] Beyond Short Snippets: Deep Networks for Video Classification [4] Learning Spatiotemporal Features with 3D Convolutional Networks [5] https://clarifai.com/
首先我们来简单的介绍一下mach-O。 什么是mach-O? Mach-O格式全称为Mach Object文件格式的缩写,是mac上可执行文件的格式,类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)。 上面第一个图是苹果给出的mach-O格式的示意图,而第二个图是我们使用machOView来分析某个可执行文件中的armv7的格式。可以看出他们两者的关系是对应的。 在machO这其中包含了很多的有效的信息,包括字符串,代码段,oc类,oc协议等各种的信息,利用这些信息我们也做到分析代码或者程序逻辑的作用,比如,下面这个数据就是我从这个machO文件里面导出来的,获取到了某个framework一个OC类中的所有基本元素。 那什么又是FatFile/FatBinary 简单来说,就是一个由不同的编译架构后的Mach-O产物所合成的集合体。例如上面我就只截取armv7的Mach-O格式座位示例, 而实际上常用的还有arm64/x86_64/i386等格式。 而实际上,包括我们使用的那些framework,大多数也是的。比如下图我们继续用machOView分析一下。 可以看到arm64/armv7架构的存在。 FrameWork跟最终可执行文件的区别在哪里? 这里我们先随便写一个简单的framework, 在这个framework中我们实现了两个oc类,如下图所示: 紧接着我们干净用machOView来看看这个新鲜出炉的framework,可以看到该FrameWork在armv7的格式下,里面存在多个.o文件。 如果我们选择其中一个继续看看的话,你就会看到一个完整的Mach-O格式的文件。 由此我们可以得知frameWork也是另外一种情况的Mach-O集合体,是由多个不同的子mach-O文件所组合而成的,他们可以单独的拆开,而可执行文件则把同一架构下的所有Mach-O文件都进行了合并,他们不能拆开,如果想要更加清晰的定义的话,可以去研究一下苹果的定义,这里不做过多的阐述。 我们能做什么? 可能到这里你还有点乱,没关系,我们直接来拆开一个framework给大家看看! 到了这一步,我们就已经知道了我们能把FrameWork中的各个子Mach-O文件拆开, 那么我们能不能把这些Mach-O文件中有效的部分重新组装一下, 生成新的一个FrameWork呢? 这必须是可以的,但是其实最重要的关键是在于怎么去确定这个Mach-O文件有没有被我们的程序使用到。 怎么确定一个Mach-O有没有被使用到? 我们直接再来一个简单的demo,尝试一下 此时进行编译 成功..... 然后拆分老的framework文件, 删掉拆分得到的MachOClassA, 并且用剩下的mach-O文件合并生成一个新的framework, 替换到工程中去并进行编译 你没看错, 他确实是失败了, 如果在程序中代码直接使用了某些类或者某些方法, 而其mach-O文件不存在的情况下, 会导致编译不过(找不到对应的方法), 这也就是说, 我们能够使用最简单粗暴的方法来判断这个machO文件是不是被需要的! 不过, 需要注意的是, category的实现方式是不一样的,故如果我们删除了category的方法, 但是直接把Mach-o删除的话, 编译时是不会报错的。有兴趣的同学可以自己去看看oc中关于category的实现。 另外还有在程序运行中动态使用的performselector方法(可以通过查询字符串列表排除)。 下面是相关的流程图。 怎么把Mach-O的删除工具应用到XCode中去 现在我们已经通过编译的手段获得了一堆mach-O文件, 但是很多都是pod中引进的, 这个时候我们需要在代码编译器执行删除.o文件的脚本 刚好Xcode确实有这么一个地方可以设置 成果 在debug模式下大概减少了0.5M, 实际二进制文件减小大概1.2M, 如果计算到最终提交到苹果并且经过DRM加密后, 预计可以减小1M左右。 PS category是需要过滤的, 这货有点特别 删除找出来的.o文件之后, 可能会引起一些特殊的情况, 当然一般是crash, 因为有一些特别的代码他们用法并不是直接引用某个方法, 而是通过NSString相关的方法来获得Sel或者Class 把查找.o文件的操作放在本地, 而在编译器上进行编译的时候就直接执行删除, 不占用编译器的时间(我们的项目要使用六个小时以上的时间来进行查找) 建议进行操作再跑一遍回归测试, 确保各个功能模块正常 其他实践与猜想以及做过的尝试 .o文件其实是可以直接引进到工程里面直接编译的, 也就是说其实可以把frameWork拆开, 然后加到工程中, 一样能够正常使用 一开始其实是想把.o文件中__text段中无用的函数进行删除, 但发现流程过于复杂, 而暂时放弃, 查找程序中无用函数的方法以后有机会再进行分享(如果这个成功的话, 估计会减小至少3~4M左右的ipa大小), 附上查找到的程序中无用方法结果的示例 其实看了上面那种方法之后, 我们紧接着又能想到, 暴力的将.m文件中的代码删除, 然后看看哪些工程中可见的代码是可以删除的(ps. 主要针对非framework, 另外也同样需要注意category以及performselector的问题, 需要配合查找字符串列表一起进行, 或者手工进行判断)。
iOS漏洞可导致Apple ID被盗 – iOS 9修复三处安全漏洞 2015年09月17日 13:53 3267 在2015.09.17发布的iOS 9的升级中,Apple修复了阿里巴巴移动安全团队所发现的三处安全漏洞:CVE-2015-5838, CVE-2015-5834, CVE-2015-5868[3]。 其中CVE-2015-5838这个漏洞可以让黑客在非越狱的iPhone 6上进行钓鱼攻击 并盗取Apple ID的密码。首先来看demo: demo虽然是在iOS 8.1.3上进行的,但直到iOS 9 苹果才进行了修复。在这个demo中,App Store是货真价实的系统,但是弹出来的登录框却不是App Store的,而是另一个在后台运行的伪造的。但因为伪造的登录框和Apple Store本身的登录框是一模一样的,所以用户很难会有察觉,并习惯性的输入了Apple ID的密码,最终导致帐号被盗。 我们知道在iOS系统的沙盒策略中,一个只能运行在自己的沙盒空间中,理论上说是无法影响其他的,但iOS系统如果在设计上的缺陷就有可能导致沙盒逃逸(比如CVE-2015-5838),随后会对系统的其他应用或文件产生影响。这个漏洞具体细节可以参考ASIACCS2015的论文[1]。 CVE-2015-5834和CVE-2015-5868是kernel层的信息泄露和代码执行的漏洞,通过这两个漏洞的组合,黑客可以获取内核信息,并执行任意代码。这两个漏洞具体的细节可以参考BlackHat 2015上的议题[2]。 给用户的建议:最好的防御方法当然是升级iOS到9.0。但因为有些iOS的老机型(比如iPhone 4等)已经无法支持最新版的iOS,所以在不升级的情况下用户要时刻警惕可疑的后台应用,在输入密码的时候尽量保证后台没有其他应用在运行。另外,千万不要安装任何企业应用,因为这些应用没有App Store的审核,所以安全性并不能得到保证。 参考文献: 1. Min Zheng, Hui Xue, Yulong Zhang, Tao Wei, John C.S. Lui. "Enpublic Apps: Security Threats Using iOS Enterprise and Developer Certificates", ASIACCS 2015 http://www.cs.cuhk.hk/~cslui/PUBLICATION/ASIACCS15.pdf 2. Lei Long, Peng Xiao, Aimin Pan. “OPTIMIZED FUZZING IOKIT IN IOS”, BlackHat 2015 https://www.blackhat.com/docs/us-15/materials/us-15-Lei-Optimized-Fuzzing-IOKit-In-iOS.pdf 3. About the security content of iOS 9 - Apple Supporthttps://support.apple.com/en-us/HT205212
PHP中的变量用一个美元符号后面跟变量名来表示。变量名是区分大小写的。 变量名与PHP中其它的标签一样遵循相同的规则。一个有效的变量名由字母或者下划线开头,后面跟上任意数量的字母,数字,或者下划线。按照正常的正则表达式,它将被表述为:'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*‘。 $this是一个特殊的变量,它不能被赋值。 基础 以$开头,跟着字母或者下划线,后面可以是数字,甚至是中文。不过要按照命名规范,使用驼峰命名. 1 2 3 4 5 6 7 8 9 10 11 12 <?php $var = 'Bob'; $Var = 'Joe'; // 区分大小写 echo "$var, $Var"; // 输出 "Bob, Joe" $4site = 'not yet'; // 非法变量名;以数字开头 $_4site = 'not yet'; // 合法变量名;以下划线开头 $i站点is = 'mansikka'; // 合法变量名;可以用中文 ?> 使用引用赋值,简单地将一个 & 符号加到将要赋值的变量前(源变量)。例如,下列代码片断将输出“My name is Bob”两次: 1 2 3 4 5 6 7 8 9 10 11 <?php $foo = 'Bob'; // 将 'Bob' 赋给 $foo $bar = &$foo; // 通过 $bar 引用 $foo $bar = "My name is $bar"; // 修改 $bar 变量 echo $bar; echo $foo; // $foo 的值也被修改 $vbar = &(24 * 7); // 非法; 引用没有名字的表达式 ?> 默认值: 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 // 未赋值和无上下文声明的变量,默认值为NULL var_dump($unset_var); // 可根据上下文 echo($unset_bool ? "true\n" : "false\n"); // String usage; outputs 'string(3) "abc"' $unset_str .= 'abc'; var_dump($unset_str); // Integer usage; outputs 'int(25)' $unset_int += 25; // 0 + 25 => 25 var_dump($unset_int); // Float/double usage; outputs 'float(1.25)' $unset_float += 1.25; var_dump($unset_float); // Array usage; outputs array(1) { [3]=> string(3) "def" } $unset_arr[3] = "def"; // array() + array(3 => "def") => array(3 => "def") var_dump($unset_arr); // Object usage; creates new stdClass object (see http://www.php.net/manual/en/reserved.classes.php) // Outputs: object(stdClass)#1 (1) { ["foo"]=> string(3) "bar" } $unset_obj->foo = 'bar'; var_dump($unset_obj); 判断是否有值: 1 2 3 4 5 6 7 8 9 <?php print isset($a); // $a is not set. Prints false. (Or more accurately prints ''.) $b = 0; // isset($b) returns true (or more accurately '1') $c = array(); // isset($c) returns true $b = null; // Now isset($b) returns false; unset($c); // Now isset($c) returns false; ?> 预定义变量 1 2 3 4 5 6 7 8 9 $_GET // 所有GET请求参数 $_POST // 所有POST请求参数 $_ENV // 环境变量 $_SERVER $_COOKIE // cookie $_REQUEST // 所有请求参数 $_SESSION // session值 PHP标记 PHP脚本以<?php 开头,以 ?> 结尾: 1 2 3 4 5 <?php echo "Hello world!"; ?> 当解析一个文件时,PHP会寻找起始和结束标记,也就是 <?php 和 ?>,这告诉PHP开始和停止解析二者之间的代码。此种解析方式使得PHP可以被嵌入到各种不同的文档中去,而任何起始和结束标记之外的部分都会被PHP解析器忽略。 PHP也允许使用短标记 <? 和 ?>,但不鼓励使用。只有通过激活php.ini中的 shortopentag配置指令或者在编译PHP时使用了配置选项 –enable-short-tags 时才能使用短标记。 如果文件内容是纯PHP代码,最好在文件末尾删除PHP结束标记。这可以避免在 PHP结束标记之后万一意外加入了空格或者换行符,会导致PHP开始输出这些空白,而脚本中此时并无输出的意图。 例如,下面的是在一个文件中的,这个文件只有PHP代码,此时建义不要加上?>结束标识: 1 2 3 4 5 6 7 <?php // Html safe containers echo "Hello world<br>"; echo "test<br>"; echo "www.henishuo.com"; 从HTML中分离 凡是在一对开始和结束标记之外的内容都会被PHP解析器忽略,这使得PHP文件可以具备混合内容。可以使PHP嵌入到HTML文档中去,如下例所示: 1 2 3 4 5 6 7 <p>This is going to be ignored by PHP and displayed by the browser.</p> <?php echo 'While this is going to be parsed.'; ?> <p>This will also be ignored by PHP and displayed by the browser.</p> 条件分离: 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $expression = true ?> <?php if ($expression == true) { echo "expression is true"; } ?> <?php if ($expression == true): ?> This will show if the expression is true. <?php else: ?> Otherwise this will show. <?php endif; ?> 指令分隔符 同C语言一样,PHP需要在每个语句后用分号结束指令。一段PHP代码中的结束标记隐含表示了一个分号;在一个PHP代码段中的最后一行可以不用分号结束。如果后面还有新行,则代码段的结束标记包含了行结束。 1 2 3 4 5 6 7 8 9 <?php echo "This is a test"; ?> <?php echo "This is a test" ?> <?php echo 'We omitted the last closing tag';?> 注释 PHP支持C、C++、Unix Shell风格(Perl 风格)的注释。例如: 1 2 3 4 5 6 7 8 9 10 11 12 <?php echo "This is a test"; // This is a one-line c++ style comment /* This is a multi line comment yet another line of comment */ echo "This is yet another test"; echo 'One Final Test'; # This is a one-line shell-style comment ?> 可以使用//或者#注释单行,可以使用/**/注释段。
本篇是笔记尝试写的第一个PHP接口,并在iOS开发中尝试应用测试。今天给大家分享如何自己写接口来测试! 相信很多朋友在开发时遇到过这样的问题:后台什么时候提供接口?怎么才提供一个接口,其他接口什么时候给出来?没有接口我们前端怎么能做得了? 哈哈!大学学完本篇就可以自己搞个接口来返回固定的死数据来测试了! 搭建PHP环境 由于本人使用的电脑是Mac,因此推荐大家使用MAMP PRO这款软件,不过是收费版,相信不想花钱的大家会有办法搞定的! MAMP PRO这款软件是集成环境软件,已经有apache、mysql、php了,具备运行解析PHP的环境了! 如果不想使用集成环境,可以自己搭建。Mac自带了apache和PHP环境的,只需要稍加配置一下就可以使用了,然后再安装一下mysql就可以了! 大家可以看一下简单版本配置:Mac配置PHP环境 这篇文章。 启动服务器 这里是以MAMP PRO软件为例,如何启动服务器。如下图,我们假设服务地址为www.api.com,然后将项目目录与这个服务地址关联起来,看图中右下角圈圈部分: 环境启动后,就可以在浏览器中直接输入www.api.com运行起来了! 开始GET接口 我们的空项目在首次运行后,会自动生成index.php这个文件,我们删除里面的内容,然后修改如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php $data = array( 'tid' => 100, 'name' => '标哥的技术博客', 'site' => 'www.huangyibiao.com'); $response = array( 'code' => 200, 'message' => 'success for request', 'data' => $data, ); echo json_encode($response); 我们这里的结构是日常App开发中最常见的返回数据结构了吧?通常都是状态码、状态信息和客户端业务数据。 PHP其实也好给力,声明数组后,通过json_encode函数就可以输出json格式数据了! iOS调GET接口 我们有了服务器,也写了接口了,那么客户端如何请求又如何获取数据呢?我们先来看一下最简单的GET请求例子,其中使用了HYBNetworking笔者的这个开源库: 1 2 3 4 5 6 7 8 NSString *url = @"http://www.api.com/index.php"; [HYBNetworking getWithUrl:url refreshCache:YES success:^(id response) { } fail:^(NSError *error) { }]; 我们看一下iOS客户端的响应结果: 从响应结果可以看到,与我们服务器接口所返回的是一致的,看下图在浏览器中访问的效果: PHP POST接口 假设我们要求传参数type过来,而且要求是数值型,用于返回不同的数据,当我们做接口测试时,就可以通过这么来干,就不需要等后台给接口了! 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 <?php $type = $_POST['type']; $data = ''; if (isset($type) && is_numeric($type) && $type >= 0) { if ($type == 1) { $data = array( 'type' => $type, 'name' => '标哥的技术博客', 'site' => 'www.huangyibiao.com'); } else if ($type == 2) { $data = array( 'type' => $type, 'name' => '公众号:标哥的技术博客', 'site' => 'weixin search: biaogedejishuboke'); } $response = array( 'code' => 200, 'message' => 'success for request', 'data' => $data, ); echo json_encode($response); return; } $response = array( 'code' => 999, 'message' => 'argument error for request', 'data' => $data, ); echo json_encode($response); iOS调POST接口 下面是iOS客户端如何调用刚才所写的PHP POST接口,其中使用了HYBNetworking笔者的这个开源库: 1 2 3 4 5 6 7 8 9 NSString *url = @"http://www.api.com/index.php"; NSDictionary *params = @{@"type" : @(1)}; [HYBNetworking postWithUrl:url refreshCache:YES params:params success:^(id response) { } fail:^(NSError *error) { }]; 我们看看效果如下,可看到如愿地接收到了服务器返回的接口数据并解析出来了: 小结 本篇就到此为止吧,相信大家若想学习它,一定会认真去操作一遍的!其实写下本篇文章之前,笔者也从尝试过! 今后会慢慢接触它,慢慢掌握它,一定会让你在工作上更加顺利的! 原文链接: http://www.huangyibiao.com/archives/1353
1、为什么要自动打包工具? 每修改一个问题,测试都让你打包一个上传fir , 你要clean -> 编译打包 -> 上传fir -> 通知测试。而且打包速度好慢,太浪费时间了。如果有一个工具能自动的帮你做完上面所有的事情,岂不是快哉? 2、网上有那么多自动打包工具,我直接下载就行了为啥还要学习? 没错网上有很多打包工具,包括github上也有一些直接从github下载并打包上传的,但是他们的不一定适合你,首先下载下来要配置各种参数,不会配,还有网上大多是针对普通项目,但是我们项目是cocoaPods管理的,编译的是 xxx.xcworkspace 不是 xxx.xcodeproj 。怎么办 , xxx.xcodeproj 自动编译后就在你项目目录下会有xxx.app 但是 xxx.xcworkspace 找不到怎么办?怎么指定目录 , 这些网上的库大都没有的。 3、需要哪些准备工作? 首先你得有装xcode , python3.5 (我装的版本,其他版本也行), 待打包的项目。安装相关软件,随便搜索下就可以了。 废话结束,开始正文。本文介绍的是自动clean本地项目,编译 打包 上传fir 邮件通知相关人员。不涉及从git上下载。原理就是利用python执行控制台命令。对 iOS项目进行打包 xcode控制台命令 xcode 控制台命令基本都是以 xcodebuild 开头的介绍几个简单的命令,大家可以在命令行试试。 xcodebuild -version 查看xcode的版本号和build的版本号 xcodebuild -showsdks 显示当前系统的SDK、及其版本 xcodebuild -list 先 cd 到工程目录下执行此命令 显示target Schemes 等 没有使用 cocoaPods 项目的编译 如果你的项目是普通的项目没有使用cocoaPods 那么 cd 到工程目录下直接执行 xcodebuild build ,就会自动编译了 参数都是默认 默认build release。 你也可以指定 xcodebuild -configuration debug build build的时候会在你工程目录下生成一个build文件夹,build/Release-iphoneos/xx.app 就是一会打包成ipa需要的文件。 第一次build速度会比较慢,要把编译环境拉下来,不要删除build文件夹,以后build 速度就会变快。 使用了 cocoaPods 项目的编译 如果不幸你也和我一样使用了cocoaPods , 其实也没啥不幸的 ,只是编译的时候就比较麻烦了 ,首先还是 cd 到项目目录 。但是你要指定编译文件和 scheme 而且还要指定build后build文件夹的位置,如果位置找不到,后面怎么自动打包ipa?。 我这里的命令大概是这样的:xcodebuild -workspace xxx.xcworkspace -scheme 你的scheme -configuration debug -derivedDataPath 指定路径 ONLY_ACTIVE_ARCH=NO 这样就能正常编译并把build指定到我们想要去的目录 打包ipa 打包ipa只要上面路径对了,打包指定从.app 文件的路径 , 打包到你指定地方就行了。 命令:xcrun -sdk iphoneos PackageApplication -v 这里填.app的路径 -o 指定存放ipa路径/文件名.ipa python实现篇 上面只是说了下编译的原理,下面看下怎么通过python自动处理这些任务 。 clean、编译、打包 首先创建一个xxx.py文件,需要你懂点python 语法,不懂就直接copy代码。不要改tab 。python的语法是严格按照tab区分的。后面我会放上我的代码,你们改改 变量就可以使用。 首先你需要引入一些外部依赖。设置编码为utf-8 1 2 3 4 5 6 7 8 9 10 # -*- coding: utf-8 -*- import os import sys import time import hashlib from email import encoders from email.header import Header from email.mime.text import MIMEText from email.utils import parseaddr, formataddr import smtplib 第一步 , 声明一些变量 1 2 3 4 5 6 7 8 # 项目根目录 project_path = "/Users/xx/project" # 编译成功后.app所在目录 app_path = "/Users/xx/project/build/Build/Products/Release-iphoneos/xxx.app" # 指定项目下编译目录 build_path = "build" # 打包后ipa存储目录 targerIPA_parth = "/Users/xx/Desktop" 第二步,clean,和创建一个文件夹,这里的示例是针对有使用cocoaPods的项目 , 如果没有使用 不用创建文件夹 ,命令自行简化 1 2 3 4 # 清理项目 创建build目录 def clean_project_mkdir_build(): os.system('cd %s;xcodebuild clean' % project_path) # clean 项目 os.system('cd %s;mkdir build' % project_path) # 创建目录 %s 是py的占位符,字符串类型。后面是真正的填充。 第三步编译项目 1 2 3 4 def build_project(): print("build release start") os.system ('cd %s;xcodebuild -list') os.system ('cd %s;xcodebuild -workspace xxx.xcworkspace -scheme xxx -configuration release -derivedDataPath %s ONLY_ACTIVE_ARCH=NO || exit' % (project_path,build_path)) 不知道scheme是啥的xcodebuild -list 自己查 第四步 打包 1 2 3 4 5 # 打包ipa 并且保存在桌面 def build_ipa(): global ipa_filename ipa_filename = time.strftime('yourproject_%Y-%m-%d-%H-%M-%S.ipa',time.localtime(time.time())) os.system ('xcrun -sdk iphoneos PackageApplication -v %s -o %s/%s' % (app_path,targerIPA_parth,ipa_filename)) 然后你现在再编写个方法,按顺序调用就可以编译打包了 ,执行完会看到桌面的ipa 1 2 3 4 5 6 7 def main(): # 清理并创建build目录 clean_project_mkdir_build() # 编译coocaPods项目文件并 执行编译目录 build_project() # 打包ipa 并制定到桌面 build_ipa() 执行就在最下面直接调用就行了 main() 上传fir 我们是把代码上传到fir测试的,如果你们用的蒲公英或者其他,请自行搜索。 通过 gem install fir-cli 如果你没有ruby环境,自行搜索 安装完成后,在命令行输入fir 回车 。会有fir的命令提示。我们上传fir需要fir的API_TOKEN , 去fir官网登录找好就能找到。 拿到那一串串字符,在变量区加上 1 2 # firm的api token fir_api_token = "xxxxxxxxxxxxxxxxxxxxxxxxxx" 然后命令传入ipa目录和token就可以上传了 1 2 3 4 5 6 7 8 #上传 def upload_fir(): if os.path.exists("%s/%s" % (targerIPA_parth,ipa_filename)): print('watting...') # 直接使用fir 有问题 这里使用了绝对地址 在终端通过 which fir 获得 ret = os.system("/usr/local/bin/fir p '%s/%s' -T '%s'" % (targerIPA_parth,ipa_filename,fir_api_token)) else: print("没有找到ipa文件") 这里也有遇到一个=坑,就是在终端直接fir 带后面的就可以执行 ,但是在这里识别不了命令,必须制定全路径,怎么找命令的全路径呢?终端输入 which fir 发邮件 具体发邮件功能看代码,这里有几个变量。我使用的是新浪邮箱发的,smtp服务器 , 如果你是 pop3 请更换,还要在邮箱里开启相应的服务 1 2 3 4 from_addr = "xxxx@sina.com" password = "*****" smtp_server = "smtp.sina.com" to_addr = 'aaa@qq.com,bbbb@qq.com' 然后发邮件的方法 我们的fir路径是固定的 1 2 3 4 5 6 7 8 9 10 11 # 发邮件 def send_mail(): msg = MIMEText('xxx iOS测试项目已经打包完毕,请前往 http://fir.im/xxxxx 下载测试!', 'plain', 'utf-8') msg['From'] = _format_addr('自动打包系统 <%s>' % from_addr) msg['To'] = _format_addr('xxx测试人员 <%s>' % to_addr) msg['Subject'] = Header('xxx iOS客户端打包程序', 'utf-8').encode() server = smtplib.SMTP(smtp_server, 25) server.set_debuglevel(1) server.login(from_addr, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit() 然后执行顺序是这样的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def main(): # 清理并创建build目录 clean_project_mkdir_build() # 编译coocaPods项目文件并 执行编译目录 build_project() # 打包ipa 并制定到桌面 build_ipa() # 上传fir upload_fir() # 发邮件 send_mail() # 执行 main() 本文重点在自动打包命令上,python代码感兴趣的可以去Python教程 学习。 本文代码已经托管在github上:https://github.com/smalldu/autoipa 原文 http://stonedu.site/2016/08/17/iOS-%E6%9C%AC%E5%9C%B0%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7/
1:应用启动时间 应用启动时,只加载启动相关的资源和必须在启动时加载的资源。 2:本地图片加载方式 本地图片加载常用方法有两种: (1),[UIImage imageNamed:@""] 图片多次使用时使用,需要使用此方式加入缓存 (2),[[UIImage alloc] initWithContentsOfFile:@""] 图片不常使用时,不使用缓存 3:不要阻塞主线程 开发中除了UI处理外,其它任务尽量放在后台线程加载。 4:UIView (1),如果你的View是透明的,需要设置opaque为YES。 (2),使用UITableView和UICollectionView的重用,并缓存动态Cell的高度。 (3), 延迟加载不急需要的Views。 (4),尽量避免代码裁剪图片,如果必须需在后台线程处理。 5:缓存 缓存需要重复显示的数据,像网络请求、图片。 6:避免对象创建时过多消耗资源 假设应用中多次使用到日期处理,将保持日期对象全局唯一。 7:释放对象 (1),ARC中创建对象过多时,内存会持续升高,在需要时添加自动释放池释放不需要的资源。 (2),及时释放自己开辟的内存空间。 8:防止循环引用 (1),在头文件中尽量避免import导入头文件,使用@class 声明类,在.m文件中#import。 (2),在设置代理/block中使用相应的关键字引用对象。 9:数据存储 数据存储大致分plist、对象归档、sqlite。NSUserDefault适合存储小量数据,私密信息使用Keychain, sqlite适合大量数据,归档性能较低,尽量避免使用。 10:内存警告处理 当系统发出内存警告时,释放不必要的数据的三种方式。 AppDelegate中实现applicationDidReceiveMemoryWarning UIViewController 重写didReceiveMemoryWarning 注册通知:UIApplicationDidReceiveMemoryWarningNotification 11:性能检测 静态分析APP:菜单-Product-Analyze 可以定位未使用的变量,内存泄漏 分析APP每个操作占用时间: Instruments-Time profiler。 动态分析内存泄漏:Instruments-Leaks。
1、前言 iOS在某些特定时刻需要把内容重一个app拷贝到另一个app 这时候我们就可以使用剪切板UIPasteboard 2、iOS自带剪切板操作的原生UI控件 在iOS中下面三个控件,自身就有复制-粘贴的功能: (1)、UITextView (2)、UITextField (3)、UIWebView 3、系统的剪切板UIPasteboard UIPasteboard类有3个初始化方法: //获取系统级别的剪切板 + (UIPasteboard *)generalPasteboard; //获取一个自定义的剪切板 name参数为此剪切板的名称 create参数用于设置当这个剪切板不存在时 是否进行创建 + (nullable UIPasteboard *)pasteboardWithName:(NSString *)pasteboardName create:(BOOL)create; //获取一个应用内可用的剪切板 + (UIPasteboard *)pasteboardWithUniqueName; 上面3个初始化方法,分别获取或创建3个级别不同的剪切板,系统级别的剪切板在整个设备中共享,即是应用程序被删掉,其向系统级的剪切板中写入的数据依然在。自定义的剪切板通过一个特定的名称字符串进行创建,它在应用程序内或者同一开发者开发的其他应用程序中可以进行数据共享。第3个方法创建的剪切板等价为使用第2个方法创建的剪切板,只是其名称字符串为nil,它通常用于当前应用内部。 注意:使用第3个方法创建的剪切板默认是不进行数据持久化的,及当应用程序退出后,剪切板中内容将别抹去。若要实现持久化,需要设置persistent属性为YES。 UIPasteboard中常用方法及属性如下: //剪切板的名称 @property(readonly,nonatomic) NSString *name; //根据名称删除一个剪切板 + (void)removePasteboardWithName:(NSString *)pasteboardName; //是否进行持久化 @property(getter=isPersistent,nonatomic) BOOL persistent; //此剪切板的改变次数 系统级别的剪切板只有当设备重新启动时 这个值才会清零 @property(readonly,nonatomic) NSInteger changeCount; 下面这些方法用于设置与获取剪切板中的数据: 最新一组数据对象的存取: //获取剪切板中最新数据的类型 - (NSArray<NSString> *)pasteboardTypes; //获取剪切板中最新数据对象是否包含某一类型的数据 - (BOOL)containsPasteboardTypes:(NSArray<NSString> *)pasteboardTypes; //将剪切板中最新数据对象某一类型的数据取出 - (nullable NSData *)dataForPasteboardType:(NSString *)pasteboardType; //将剪切板中最新数据对象某一类型的值取出 - (nullable id)valueForPasteboardType:(NSString *)pasteboardType; //为剪切板中最新数据对应的某一数据类型设置值 - (void)setValue:(id)value forPasteboardType:(NSString *)pasteboardType; //为剪切板中最新数据对应的某一数据类型设置数据 - (void)setData:(NSData *)data forPasteboardType:(NSString *)pasteboardType; 多组数据对象的存取: //数据组数 @property(readonly,nonatomic) NSInteger numberOfItems; //获取一组数据对象包含的数据类型 - (nullable NSArray *)pasteboardTypesForItemSet:(nullable NSIndexSet*)itemSet; //获取一组数据对象中是否包含某些数据类型 - (BOOL)containsPasteboardTypes:(NSArray<NSString> *)pasteboardTypes inItemSet:(nullable NSIndexSet *)itemSet; //根据数据类型获取一组数据对象 - (nullable NSIndexSet *)itemSetWithPasteboardTypes:(NSArray *)pasteboardTypes; //根据数据类型获取一组数据的值 - (nullable NSArray *)valuesForPasteboardType:(NSString *)pasteboardType inItemSet:(nullable NSIndexSet *)itemSet; //根据数据类型获取一组数据的NSData数据 - (nullable NSArray *)dataForPasteboardType:(NSString *)pasteboardType inItemSet:(nullable NSIndexSet *)itemSet; //所有数据对象 @property(nonatomic,copy) NSArray *items; //添加一组数据对象 - (void)addItems:(NSArray<NSDictionary> *> *)items; 上面方法中很多需要传入数据类型参数,这些参数是系统定义好的一些字符窜,如下: //所有字符串类型数据的类型定义字符串数组 UIKIT_EXTERN NSArray<NSString> *UIPasteboardTypeListString; //所有URL类型数据的类型定义字符串数组 UIKIT_EXTERN NSArray<NSString> *UIPasteboardTypeListURL; //所有图片数据的类型定义字符串数据 UIKIT_EXTERN NSArray<NSString> *UIPasteboardTypeListImage; //所有颜色数据的类型定义字符串数组 UIKIT_EXTERN NSArray<NSString> *UIPasteboardTypeListColor; 相比于上面两组方法,下面这些方法更加面向对象,在开发中使用更加方便与快捷: //获取或设置剪切板中的字符串数据 @property(nullable,nonatomic,copy) NSString *string; //获取或设置剪切板中的字符串数组 @property(nullable,nonatomic,copy) NSArray<NSString> *strings; //获取或设置剪切板中的URL数据 @property(nullable,nonatomic,copy) NSURL *URL; //获取或设置剪切板中的URL数组 @property(nullable,nonatomic,copy) NSArray<NSURL> *URLs; //获取或s何止剪切板中的图片数据 @property(nullable,nonatomic,copy) UIImage *image; //获取或设置剪切板中的图片数组 @property(nullable,nonatomic,copy) NSArray<UIImage> *images; //获取或设置剪切板中的颜色数据 @property(nullable,nonatomic,copy) UIColor *color; //获取或设置剪切板中的颜色数组 @property(nullable,nonatomic,copy) NSArray<UIColor> *colors; 对剪切板的某些操作会触发如下通知: //剪切板内容发生变化时发送的通知 UIKIT_EXTERN NSString *const UIPasteboardChangedNotification; //剪切板数据类型键值增加时发送的通知 UIKIT_EXTERN NSString *const UIPasteboardChangedTypesAddedKey; //剪切板数据类型键值移除时发送的通知 UIKIT_EXTERN NSString *const UIPasteboardChangedTypesRemovedKey; //剪切板被删除时发送的通知 UIKIT_EXTERN NSString *const UIPasteboardRemovedNotification; 未完待续...
iOS应用支持IPV6,就那点事儿 果然是苹果打个哈欠,iOS行业内就得起一次风暴呀。自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPV6-Only的网络,大家便开始热火朝天的研究如何支持IPV6,以及应用中哪些模块目前不支持IPV6。 一、IPV6-Only支持是啥? 首先IPV6,是对IPV4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IPV6 DNS64/NAT64网络之后,设备被分配的地址会变成IPV6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64 Server查询IPv6的地址,如果查询不到,再向DNS Server查询IPv4地址,通过DNS64 Server合成一个IPV6的地址,最终将一个IPV6的地址返回给客户端。如图所示: NAT64-DNS64-ResolutionOfIPv4_2x.png 在Mac OS 10.11+的双网卡的Mac机器(以太网口+无线网卡),我们可以通过模拟构建这么一个local IPv6 DNS64/NAT64 的网络环境去测试应用是否支持IPV6-Only网络,大概原理如下: local_ipv6_dns64_nat64_network_2x.png 参考资料: https://developer.apple.com/library/mac/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1 二、Apple如何审核支持IPV6-Only? 首先第一点:这里说的支持IPV6-Only网络,其实就是说让应用在 IPv6 DNS64/NAT64 网络环境下仍然能够正常运行。但是考虑到我们目前的实际网络环境仍然是IPV4网络,所以应用需要能够同时保证IPV4和IPV6环境下的可用性。从这点来说,苹果不会去扫描IPV4的专有API来拒绝审核通过,因为IPV4的API和IPV6的API调用都会同时存在于代码中(不过为了减小审核被拒风险,建议将IPV4专有API通过IPV6的兼容API来替换)。 其次第二点:Apple官方声明iOS9开始向IPV6支持过渡,在iOS9.2+支持通过getaddrInfo方法将IPV4地址合成IPV6地址(The ability to synthesize IPv6 addresses was added to getaddrinfo in iOS 9.2 and OS X 10.11.2)。其提供的Reachability库在iOS8系统下,当从IPV4切换到IPV6网络,或者从IPV6网络切换到IPV4,是无法监控到网络状态的变化。也有一些开发者针对这些Bug询问Apple的审核部门,给予的答复是只需要在苹果最新的系统上保证IPV6的兼容性即可。 最后第三点:只要应用的主流程支持IPV6,通过苹果审核即可。对于不支持IPV6的模块,考虑到我们现实IPV6网络的部署还需要一段时间,短时间内不会影响我们用户的使用。但随着4G网络IPV6的部署,这部分模块还是需要逐渐安排人力进行支持。 追加第四点:如果应用一直直接使用IPV4地址通过NSURLConenction或者NSURLSession进行网络请求(一般需要服务器允许,且客户端需要在header中伪装host);经测试,IPV6网络环境下,直接使用IPV4地址在iOS9及以上的系统仍然能够正常访问;在iOS8.4及以下不能正常访问;这一点苹果的解释和建议是这样的: Note: In iOS 9 and OS X 10.11 and later, NSURLSession and CFNetwork automatically synthesize IPv6 addresses from IPv4 literals locally on devices operating on DNS64/NAT64 networks. However, you should still work to rid your code of IP address literals. 三、应用如何支持IPV6-Only? 对于如何支持IPV6-Only,官方给出了如下几点标准:(这里就不对其进行解释了,大家看上面的参考链接即可) 1. Use High-Level Networking Frameworks; 2. Don’t Use IP Address Literals; 3. Check Source Code for IPv6 DNS64/NAT64 Incompatibilities; 4. Use System APIs to Synthesize IPv6 Addresses; 3.1 NSURLConnection是否支持IPV6? 官方的这句话让我们疑惑顿生:using high-level networking APIs such as NSURLSession and the CFNetwork frameworks and you connect by name, you should not need to change anything for your app to work with IPv6 addresses 只说了NSURLSession和CFNetwork的API不需要改变,但是并没有提及到NSURLConnection。 从上文的参考资料中,我们看到NSURLSession、NSURLConnection同属于Cocoa的url loading system,可以猜测出NSURLConnection在ios9上是支持IPV6的。 应用里面的API网络请求,大家一般都会选择AFNetworking进行请求发送,由于历史原因,应用的代码基本上都深度引用了AFHTTPRequestOperation类,所以目前API网络请求均需要通过NSURLConnection发送出去,所以必须确认NSURLConnection是否支持IPV6. 经过测试,NSURLConnection在最新的iOS9系统上是支持IPV6的。 3.2 Cocoa的URL Loading System从iOS哪个版本开始支持IPV6? 目前我们的应用最低版本还需要支持iOS7,虽然苹果只要求最新版本支持IPV6-Only,但是出于对用户负责的态度,我们仍然需要搞清楚在低版本上URL Loading System的API是否支持IPV6. (to fix me, make some experiments)待续~~~ 3.3 Reachability是否需要修改支持IPV6? 我们可以查到应用中大量使用了Reachability进行网络状态判断,但是在里面却使用了IPV4的专用API。 在Pods:Reachability中 AF_INET Files:Reachability.m struct sockaddr_in Files:Reachability.h , Reachability.m 那Reachability应该如何支持IPV6呢? (1)目前Github的开源库Reachability的最新版本是3.2,苹果也出了一个Support IPV6 的Reachability的官方样例,我们比较了一下源码,跟Github上的Reachability没有什么差异。 (2)我们通常都是通过一个0.0.0.0 (ZeroAddress)去开启网络状态监控,经过我们测试,在iOS9以上的系统上IPV4和IPV6网络环境均能够正常使用;但是在iOS8上IPV4和IPV6相互切换的时候无法监控到网络状态的变化,可能是因为苹果在iOS8上还并没有对IPV6进行相关支持相关。(但是这仍然满足苹果要求在最新系统版本上支持IPV6的网络)。 (3)当大家都在要求Reachability添加对于IPV6的支持,其实苹果在iOS9以上对Zero Address进行了特别处理,官方发言是这样的: reachabilityForInternetConnection: This monitors the address 0.0.0.0, which reachability treats as a special token that causes it to actually monitor the general routing status of the device, both IPv4 and IPv6. + (instancetype)reachabilityForInternetConnection { struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress]; } 综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPV6和IPV4,但是在iOS9以下会存在bug,但是苹果审核并不关心。 四、底层的socket API如何同时支持IPV4和IPV6? 由于在应用中使用了网络诊断的组件,大量使用了底层的 socket API,所以对于IPV6支持,这块是个重头戏。如果你的应用中使用了长连接,其必然会使用底层socket API,这一块也是需要支持IPV6的。 对于Socket如何同时支持IPV4和IPV6,可以参考谷歌的开源库CocoaAsyncSocket. 下面我针对我们的开源 网络诊断组件, 说一下是如何同时支持IPV4和IPV6的。 开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git 这个网络诊断组件的主要功能如下: 本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析); 通过TCP Connect监测到域名的连通性; 通过Ping 监测到目标主机的连通耗时; 通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时; 4.1 IP地址从二进制到符号的转化 之前我们都是通过inet_ntoa()进行二进制到符号,这个API只能转化IPV4地址。而inet_ntop()能够兼容转化IPV4和IPV6地址。 写了一个公用的in6_addr的转化方法如下: //for IPV6 +(NSString *)formatIPV6Address:(struct in6_addr)ipv6Addr{ NSString *address = nil; char dstStr[INET6_ADDRSTRLEN]; char srcStr[INET6_ADDRSTRLEN]; memcpy(srcStr, &ipv6Addr, sizeof(struct in6_addr)); if(inet_ntop(AF_INET6, srcStr, dstStr, INET6_ADDRSTRLEN) != NULL){ address = [NSString stringWithUTF8String:dstStr]; } return address; } //for IPV4 +(NSString *)formatIPV4Address:(struct in_addr)ipv4Addr{ NSString *address = nil; char dstStr[INET_ADDRSTRLEN]; char srcStr[INET_ADDRSTRLEN]; memcpy(srcStr, &ipv4Addr, sizeof(struct in_addr)); if(inet_ntop(AF_INET, srcStr, dstStr, INET_ADDRSTRLEN) != NULL){ address = [NSString stringWithUTF8String:dstStr]; } return address; } 4.2 本机IP获取支持IPV6 相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(Wifi)、pdp_ip0(移动网络)的ip地址。 注意: (1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。 (2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。 + (NSString *)deviceIPAdress { while (temp_addr != NULL) { NSLog(@"ifa_name===%@",[NSString stringWithUTF8String:temp_addr->ifa_name]); // Check if interface is en0 which is the wifi connection on the iPhone if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"] || [[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"pdp_ip0"]) { //如果是IPV4地址,直接转化 if (temp_addr->ifa_addr->sa_family == AF_INET){ // Get NSString from C String address = [self formatIPV4Address:((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr]; } //如果是IPV6地址 else if (temp_addr->ifa_addr->sa_family == AF_INET6){ address = [self formatIPV6Address:((struct sockaddr_in6 *)temp_addr->ifa_addr)->sin6_addr]; if (address && ![address isEqualToString:@""] && ![address.uppercaseString hasPrefix:@"FE80"]) break; } } temp_addr = temp_addr->ifa_next; } } } 4.3 设备网关地址获取获取支持IPV6 其实是在IPV4获取网关地址的源码的基础上进行了修改,初开把AF_INET->AF_INET6, sockaddr -> sockaddr_in6之外,还需要注意如下修改,就是拷贝的地址字节数。去掉了ROUNDUP的处理。 (解析出来的地址老是少了4个字节,结果是偏移量搞错了,纠结了半天),具体参考源码库。 /* net.route.0.inet.flags.gateway */ int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY}; if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &l, 0, 0) < 0) { address = @"192.168.0.1"; } .... //for IPV4 for (i = 0; i < RTAX_MAX; i++) { if (rt->rtm_addrs & (1 << i)) { sa_tab[i] = sa; sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len)); } else { sa_tab[i] = NULL; } } //for IPV6 for (i = 0; i < RTAX_MAX; i++) { if (rt->rtm_addrs & (1 << i)) { sa_tab[i] = sa; sa = (struct sockaddr_in6 *)((char *)sa + sa->sin6_len); } else { sa_tab[i] = NULL; } } 4.4 设备DNS地址获取支持IPV6 IPV4时只需要通过res_ninit进行初始化就可以获取,但是在IPV6环境下需要通过res_getservers()接口才能获取。 +(NSArray *)outPutDNSServers{ res_state res = malloc(sizeof(struct __res_state)); int result = res_ninit(res); NSMutableArray *servers = [[NSMutableArray alloc] init]; if (result == 0) { union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union)); res_getservers(res, addr_union, res->nscount); for (int i = 0; i < res->nscount; i++) { if (addr_union[i].sin.sin_family == AF_INET) { char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN); NSString *dnsIP = [NSString stringWithUTF8String:ip]; [servers addObject:dnsIP]; NSLog(@"IPv4 DNS IP: %@", dnsIP); } else if (addr_union[i].sin6.sin6_family == AF_INET6) { char ip[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN); NSString *dnsIP = [NSString stringWithUTF8String:ip]; [servers addObject:dnsIP]; NSLog(@"IPv6 DNS IP: %@", dnsIP); } else { NSLog(@"Undefined family."); } } } res_nclose(res); free(res); return [NSArray arrayWithArray:servers]; } 4.4 域名DNS地址获取支持IPV6 在IPV4网络下我们通过gethostname获取,而在IPV6环境下,通过新的gethostbyname2函数获取。 //ipv4 phot = gethostbyname(hostN); //ipv6 phot = gethostbyname2(hostN, AF_INET6); 4.5 ping方案支持IPV6 Apple的官方提供了最新的支持IPV6的ping方案,参考地址如下:https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html 只是需要注意的是: (1)返回的packet去掉了IPHeader部分,IPV6的header部分也不返回TTL(Time to Live)字段; (2)IPV6的ICMP报文不进行checkSum的处理; 4.6 traceRoute方案支持IPV6 其实是通过创建socket套接字模拟ICMP报文的发送,以计算耗时; 两个关键的地方需要注意: (1)IPV6中去掉IP_TTL字段,改用跳数IPV6_UNICAST_HOPS来表示; (2)sendto方法可以兼容支持IPV4和IPV6,但是需要最后一个参数,制定目标IP地址的大小;因为前一个参数只是指明了IP地址的开始地址。千万不要用统一的sizeof(struct sockaddr), 因为sockaddr_in 和 sockaddr都是16个字节,两者可以通用,但是sockaddr_in6的数据结构是28个字节,如果不显式指定,sendto方法就会一直返回-1,erroNo报22 Invalid argument的错误。 关键代码如下:(完整代码参考开源组件) //构造通用的IP地址结构stuck sockaddr NSString *ipAddr0 = [serverDNSs objectAtIndex:0]; //设置server主机的套接口地址 NSData *addrData = nil; BOOL isIPV6 = NO; if ([ipAddr0 rangeOfString:@":"].location == NSNotFound) { isIPV6 = NO; struct sockaddr_in nativeAddr4; memset(&nativeAddr4, 0, sizeof(nativeAddr4)); nativeAddr4.sin_len = sizeof(nativeAddr4); nativeAddr4.sin_family = AF_INET; nativeAddr4.sin_port = htons(udpPort); inet_pton(AF_INET, ipAddr0.UTF8String, &nativeAddr4.sin_addr.s_addr); addrData = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; } else { isIPV6 = YES; struct sockaddr_in6 nativeAddr6; memset(&nativeAddr6, 0, sizeof(nativeAddr6)); nativeAddr6.sin6_len = sizeof(nativeAddr6); nativeAddr6.sin6_family = AF_INET6; nativeAddr6.sin6_port = htons(udpPort); inet_pton(AF_INET6, ipAddr0.UTF8String, &nativeAddr6.sin6_addr); addrData = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; } struct sockaddr *destination; destination = (struct sockaddr *)[addrData bytes]; //创建socket if ((recv_sock = socket(destination->sa_family, SOCK_DGRAM, isIPV6?IPPROTO_ICMPV6:IPPROTO_ICMP)) < 0) if ((send_sock = socket(destination->sa_family, SOCK_DGRAM, 0)) < 0) //设置sender 套接字的ttl if ((isIPV6? setsockopt(send_sock,IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)): setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))) < 0) //发送成功返回值等于发送消息的长度 ssize_t sentLen = sendto(send_sock, cmsg, sizeof(cmsg), 0, (struct sockaddr *)destination, isIPV6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in)); 原文链接: http://www.jianshu.com/p/a6bab07c4062
这一篇文章着重于保护重要数据不被攻击者使用Cycript或者Runtime修改,概要内容如下: 防止choose(类名) 禁忌,二重存在 自己的内存块 虚伪的setter/getter 加密内存数据 English version is here 以下内容均以此假想情况为基础: 我们有一个Person类,它的定义如下: 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 @interface Person : NSObject { NSString * _name; int _age; } @property (strong, nonatomic, readonly) NSString * name; @property (nonatomic, readonly) int age; - (instancetype)initWithName:(NSString *)name age:(int)age; @end @implementation Person @synthesize name = _name; @synthesize age = _age; - (instancetype)initWithName:(NSString *)name age:(int)age{ self = [self init]; if (self) { _name = name; _age = age; } return self; } - (void)setName:(NSString *)name { if (name != _name) { _name = name ; } } - (void)setAge:(int)age { _age = age; } - (NSString *)name { return _name; } - (int)age { return _age; } @end 现在我们需要保护这个类的数据,虽然我们在@property里声明了这两个都是readonly,但是因为Objective-C的runtime特性,这个属性说了基本等于没说(对于破解者而言)。 那么我们要怎么做才能保护呢? 防止choose(类名) 我们知道,在Cycript中可以很方便的使用choose(类名)来获取到App中该类所有的实例变量(图1),那么我们就先从这里下手吧! 解决方案: 重载- (NSString *)description方法。效果如图2所示。 1 2 3 - (NSString *)description { return [NSString stringWithFormat:@"This person is named %@, aged %d.", self.name, self.age]; } 禁忌,二重存在 上面虽然在cycript中用choose函数拿不到了,但是如果一开始就被Hook了init方法怎么办呢? 解决方案:memcpy一份。 首先确定Person类实例的大小:(类指针大小+所有成员变量大小) 1 ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int); 然后就可以愉快的memcpy了: 1 2 3 Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0]; void * superman = malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size); 在用的时候,通过__bridge转换: 1 [(__bridge Person *)superman setName:@"Superman"]; 代码片段: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0]; ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int); void * superman = malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size); [(__bridge Person *)superman setName:@"Superman"]; [(__bridge Person *)superman setAge:20]; /** * @brief 为了演示方便加的while */ while (1) { NSLog(@"Normal: %p %@",normal_man, [normal_man name]); NSLog(@"Superman: %p %@",superman, [(__bridge Person *)superman name]); sleep(2); } 那么为了模拟实际情况(即init方法被Hook,拿到了normal_man的地址),我们直接在NSLog里输出。 使用Cycript攻击的实际效果如图3、图4: 通过Hook init方法,拿到了normal_man的地址0x7fbffbe06b00。 在Cycript中使用choose,只能看见两个字符串。现在直接调用[#0x7fbffbe06b00 setName:@"Cracker"];更改name属性。 可以看到normal_man的name的确被更改了。而我们memcpy的superman表示无压力。 那么superman的地址也被找到了的话,怎么办呢?如图5 P.S 事实上,它也的确被找到了,cycript会检索所有malloc的内存,图4、图5里,choose执行后的两句NSString就是证明,只不过因为我们重载了description方法,才没有直接看到地址。 自己的内存块 那么我们把这个normal_man复制到自己的一个内存区块如何呢?正好借用之前写的MemoryRegion。试试看吧! 代码片段:(其余部分与上面的相同) 1 2 3 ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int); MemoryRegion mmgr = MemoryRegion(1024); void * superman = mmgr.malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size); 实际效果(图6): 可以看到,现在choose找不到处于MemoryRegion中的superman。 不过就算找不到,Cracker还可以Hook这个类的setter和getter呀!我们又要如何应对呢? 虚伪的setter/getter 让我们把setter和getter改成这个样子: 1 2 3 4 5 6 7 8 9 10 11 12 - (void)setName:(NSString *)name { _name = @"Naive"; } - (void)setAge:(int)age { _age = INT32_MAX; } - (NSString *)name { return @"233"; } - (int)age { return INT32_MIN; } 这样Cracker们通过setter方法就改不了了,也不能通过getter来获取,只能HookIvar了。当然我们也是,那么我们自己要怎么修改呢?添加两个C函数吧! 1 2 3 4 5 6 7 8 __attribute__((always_inline)) void setName(void * obj, NSString * newName) { void * ptr = (void *)((long)(long *)(obj) + sizeof(Person *)); memcpy(ptr, (void*) &newName, sizeof(char) * newName.length); } __attribute__((always_inline)) void setAge(void * obj, int newAge) { void * ptr = (void *)((long)(long *)obj + sizeof(Person *) + sizeof(NSString *)); memcpy(ptr, &newAge, sizeof(int)); } 在修改的时候使用: 1 2 setName(superman, @"Superman"); setAge (superman, 20); 在获取的时候: 1 NSLog(@"This person is named %@, aged %d", *((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *)))); 加密内存区块 在我们把Person类改成上面那个样子之后,已经能阻止大部分只用cycript就想调戏我们的App的人了。 然而,如果Cracker们搜索内存的话,还是有可能找到一些数据的,比如这里superman的年龄, superman的内存地址是0x102800f00,_age在(0x102800f00 + sizeof(Person *) + sizeof(NSString *)),也就是0x102800f10,如图7。 那么我们不用的时候加密这块内存,用的时候再解密,演示用的加密、解密函数如下, 1 2 3 4 5 6 7 8 9 10 11 12 __attribute__((always_inline)) void encryptSuperman(void ** data_ptr, ssize_t length) { char * data = (char *) * data_ptr; for (ssize_t i = 0; i < length; i++) { data[i] ^= 0xBBC - i; } } __attribute__((always_inline)) void decryptSuperman(void ** data_ptr, ssize_t length) { char * data = (char *) * data_ptr; for (ssize_t i = 0; i < length; i++) { data[i] ^= 0xBBC - i; } } 使用代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Person * normal_man = [[Person alloc] initWithName:@"Nobody" age:0]; ssize_t object_size = sizeof(Person *) + sizeof(NSString *) + sizeof(int); MemoryRegion mmgr = MemoryRegion(1024); void * superman = mmgr.malloc(object_size); memcpy(superman, (__bridge void *)normal_man, object_size); setName(superman, @"Superman"); setAge (superman, 20); encryptSuperman(&superman, object_size); /** * @brief 为了演示方便加的while */ while (1) { NSLog(@"Normal: %p %@",normal_man,[normal_man name]); NSLog(@"Superman: %p",superman); decryptSuperman(&superman, object_size); NSLog(@"This person is named %@, aged %d",*((CFStringRef *)(void*)((long)(long *)(superman) + sizeof(Person *))), *((int *)((long)(long *)superman + sizeof(Person *) + sizeof(NSString *)))); encryptSuperman(&superman, object_size); sleep(5); } 现在再来看看内存里的数据(图8): 嗯,似乎是没问题了呢~ 完整示例代码,https://github.com/BlueCocoa/HookMeIfYouCan http://www.cocoachina.com/ios/20150511/11801.html
cycript 详细使用 http://iphonedevwiki.net/index.php/Cycript_Tricks http://www.jianshu.com/p/bb65b76c1b4b Class-dump (获取头文件) http://stevenygard.com/projects/class-dump/ 砸壳后的APP,进入文件所在目录 输入命令 class-dump -H eShop.app -o eShop.h OK! // 获取所有内存信息 Liamde-iPhone:/usr/bin root# ps -e 根据pid 进入 Liamde-iPhone:/usr/bin root# cycript -p 24906
一、class-dump 简介:顾名思义,就是用来导出目标对象的class信息的工具,私有方法声明也能导出来。 原理:利用 Objective-C语言的 runtime 特性,将存 在Mach-O 文件中的头文件信息提 出来,并生成对应的 .h 文件。 使用方法: 1,下载然后将class-dump 复制到“ /usr/bin”目录下。 2,执行sudo chmod 777 /usr/bin/class-dump”命令赋予其执行权限。 3,class-dump执行: class-dump -S -s -H /Applications/Calculator.app -o /Users/zhangdasen/Desktop/test 4,上面一段代码就是导出Mac下计算器app的头文件,到桌面test文件夹,并且排序,class-dump的一些参数,大家可以自己在命令行输入class-dump然后回车就有相关说明。 使用注意 从 AppStore 下 的 App 都是经过加密的,可执行文件被加上了一层"壳"。 class-dump 应付不了这样的文件,此时使用 class-dump 看上去会“失效”。还得先用别的工具把壳砸开才行,这个后面会说到。 class-dump下载地址:http://stevenygard.com/projects/class-dump 二、Theos越狱开发工具包 简介 Theos:是一个越狱开发工具包,由iOS越狱界知名人士Dustin Howett(@DHowett)开发并分享到 GitHub 上。 iOSOpenDev:是整合在 Xcode里的越狱开发工具, 熟悉Xcode 的朋友可能会对它更感兴趣。但逆向工程接触底层知识较多,很多东西无法自动化,因此推荐使用整合度并不算高的 Theos,当你手动完成一个又一个练习时,对逆向工程的理解一定会更深。 (1) Theos的安装: 1,设置xcode 假设安装了3 个 Xcode,并将它们分别命名为 Xcode1.app、Xcode2.app 和 Xcode3.app, 要指定 Xcode3 为活动 Xcode,则运行如下命令 : sudo xcode-select -s /Applications/Xcode3.app/Contents/Developer 2, Theos下载 从 GitHub 上下 Theos, 操作如下:(在终端中执行) export THEOS=/opt/theos sudo git clone git://github.com/DHowett/theos.git$THEOS export THEOS=/opt/theos 这句话是设置环境变量,上面两句话指的就是git克隆theos到系统的/opt/theos目录下. 小问题:git 克隆下来的theos由于缺少文件,在后面make packget install 会提示缺少 _Prefix/NullabilityCompat.h 等文件,需要我们去https://github.com/theos/headers 把这个头文件放到/opt/theos/include目录中就行了。(可直接克隆到目录中去) sudo git clone https://github.com/theos/headers /opt/theos/include 不过我这边已经有一个封好的theos,下面会提到。 3,配置ldid ldid 是专门用来 名 iOS 可执行文件的工具,用以在越狱 iOS 中 代 Xcode 自带的codesign。从 http://joedj.net/ldid 下 ldid,把它放在“ /opt/theos/bin/ ”下,然后用以下命令赋予它可执行权限: sudo chmod 777 /opt/theos/bin/ldid 4,配置CydiaSubstrate 因为Theos 的一个 bug,它无法自动生成一个有效的 libsubstrate.dylib 文件,需要手动操作。 在 Cydia 中搜索安装“CydiaSubstrate”,然后用 iFunBox 或 scp 等方式将 iOS 上的“/Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate” 到 OSX 中,将其重命名为 libsubstrate.dylib 后放到 “ /opt/theos/vendor/lib/libsubstrate.dylib”中, 替换掉无效的文件即可。 5,dpkg-deb deb 是越狱开发安装包的标准格式,dpkg-deb 是一个用于操作 deb 文件的工具,有了这个工具,Theos 才能正确地把工程打包成为 deb 文件。 从 https://raw.githubusercontent.com/DHowett/dm.pl/master/dm.pl 下 dm.pl,将其重命名为 dpkg-deb 后,放到“/opt/theos/bin/”目录下,然后用以下命令赋予其可执行权限: sudo chmod 777 /opt/theos/bin/dpkg-deb Theos操作这些移动文件设置权限这些步骤很啰嗦。 鉴于这些,我自己这边已经封装了一个集成好的Theos,附带一个自动下载的小脚本TheosScript.sh,不需要手动添加ldid、libsubstrate、dpkg-deb等。 脚本下载地址:https://github.com/DaSens/Theos-Script 进行简单演示: 进行简单演示 (2) Theos的用法介绍: 1,更改工作目录 更改工作目录至常用的 iOS 工程目录(如 “ /Users/zhangdasen/Code/”)注:为何要做这一步呢,因为使用Theos创建的工程,是你在哪个目录执行的nic.pl启动的,就会在创建完成后在这个目录生成创建的Theos项目。 2,创建工程 终端输入“/opt/theos/bin/nic.pl”,启动 NIC(New Instance Creator),下面进行简单演示: 3,简单说明 上面创建工程输入了一大堆东西,下面用一张图来说明: 4,工程文件说明 创建好工程就会生成四个文件: Makefile、Tweak.xm、control、iOSREProject.plist 下面进行简单说明。 (1) Makefile Makefile 文件指定工程用到的文件、框架、库等信息,将整个过程自动化,下图为里面内容说明。下图是我自己修改过后的Makefile文件。 补充: 1,导入 private framework iOSREProject_PRIVATE_FRAMEWORKS = private framework name 2,链接 Mach-O 对象(Mach-O object) iOSREProject_LDFLAGS = -lz –lsqlite3.0 –dylib1.o (2) Tweak.xm 用 Theos 创建 tweak 工程, 认生成的源文件是 Tweak.xm。 如果后缀名是单独一个“ x”,说明源文件支持 Logos 和 C 语法; 如果后缀名是“ xm ”,说明源文件支持 Logos 和 C/C++ 语法,与“ m ”和“ mm ”的区别类似。 Tweak.xm 语法众多,在这里简单介绍一些,详细语法大家可以去看书籍。 ● %hook 指定需要 hook 的 class, 必须以 %end 结尾 %hook SpringBoard - (void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button."); %orig; // call the original _menuButtonDown: } %end ● %orig该指 在 %hook 内部使用,执行被 (hook)的 数的原始代码,如下: %hook SpringBoard - (void)_menuButtonDown:(id)down { NSLog(@"You've pressed home button."); %orig; // call the original _menuButtonDown: } %end 还可以利用 %orig 更改原始 数的参数,例如: %hook SBLockScreenDateViewController - (void)setCustomSubtitleText:(id)arg1 withColor:(id)arg2 { %orig(@"iOS 8 App Reverse Engineering", arg2); } %end 这样一来, 锁屏界面原本显示日期的地方就变成了如图的样子。 ● %group 该指 用于将 %hook 分组,便于代码管理及按条件初始化分组(含义 后有 细解 ),必须以 %end 结 ;一个 %group可以包含多个 %hook,所有不 于某个自定义 group 的 %hook会被隐式归类到 %group _ungrouped 中。 用法: %group iOS7Hook %hook iOS7Class - (id)iOS7Method { id result = %orig; NSLog(@"This class & method only exist in iOS 7."); return result; } %end %end // iOS7Hook %group iOS8Hook %hook iOS8Class - (id)iOS8Method { id result = %orig; NSLog(@"This class & method only exist in iOS 8."); return result; } %end %end // iOS8Hook ● %init 该指令用于初始化某个 %group,必须在 %hook 或 %ctor 内调用;如果带参数,则初始化指定的 group,如果不带参数,则初始化 _ungrouped,如下: #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10 #endif %hook SpringBoard - (void)applicationDidFinishLaunching:(id)application { %orig; %init; // Equals to %init(_ungrouped) if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS7Hook); if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS8Hook); }%end 只有调用了 %init,对应的 %group 才能起作用,切 切 ! ● %new 在 %hook 内部使用,给一个现有 class 加新函数,功能与 class_addMethod 相同。它的用法如下: %hook SpringBoard %new - (void)namespaceNewMethod { NSLog(@"We've added a new method to SpringBoard."); } %end ● %ctor tweak的constructor,完成初始化工作;如果不显式定义,Theos会自动生成一个%ctor,并在其中调用 %init(_ungrouped)。因此, %hook SpringBoard - (void)reboot { NSLog(@"If rebooting doesn't work then I'm screwed."); %orig; } %end 可以成功生效,因为 Theos 隐式定义了如下内容:%ctor { %init(_ungrouped); } %hook SpringBoard - (void)reboot{ NSLog(@"If rebooting doesn't work then I'm screwed."); %orig; } %end 里的 %hook无法生效,因为这里显式定义了%ctor,却没有显式调用 %init, %group(_ungrouped) 不起作用。 %ctor 一般可以用来初始化 %group,以及进行 MSHookFunction 等操作,如下: #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 #define kCFCoreFoundationVersionNumber_iOS_8_0 1140.10 #endif %ctor{ %init; if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0 && kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS7Hook); if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) %init(iOS8Hook); MSHookFunction((void *)&AudioServicesPlaySystemSound, (void *)&replaced_AudioServicesPlaySystemSound, (void **)&original_AudioServicesPlaySystemSound); } 注意,%ctor 不需要以 %end 结 。 ● %c 该指 的作用等同于 objc_getClass 或 NSClassFromString,即动态获 一个类的定义,在 %hook 或 %ctor 内使用。 (3) control文件 control文件 录了deb包管理系统所需的基本信息,会被打包进deb包里。 iOSREProject 里 control 文件的内容如下: control 文件中可以自定义的字段还有很多,但上面这些信息就已经足够了。更全面的 说明可以参阅 debian 的官方网站(http://www.debian.org/doc/debian-policy/ch-controlfields.html) (3)iOSREProject.plist文件 简单来说就是里面描述了tweak 的作用范围,也就是需要作用的APP的bundle identifier。 也就是在创建项目的时候填写的这个地方: 待续。。。。。。 下个文章继续更新内容 ---- 手动HOOK一个自己的APP,和剩余越狱开发工具简单介绍。 由于本书笔者还没看完呢,所以更新会比较慢,或者有段时间不更新,再次说明,整理这些内容只是为了自己方便查阅笔记,顺便分享给大家,没有它意,希望大家支持原版书籍。 iOS逆向知识,千变万化,无穷无尽,需要学习的太多了,国内文章知识还是比较少,逆向的书籍也是比较少,逆向开发方面的进步也需要我们国人也要努力。 参考书籍: 沙梓社 吴航 * 著 感谢作者。
机器视觉开源处理库汇总 从cvchina搞到的机器视觉开源处理库汇总,转来了,很给力,还在不断更新。。。 通用库/General Library OpenCV 无需多言。 RAVL Recognition And Vision Library. 线程安全。强大的IO机制。包含AAM。 CImg 很酷的一个图像处理包。整个库只有一个头文件。包含一个基于PDE的光流算法。 图像,视频IO/Image, Video IO FreeImage DevIL ImageMagick FFMPEG VideoInput portVideo AR相关/Augmented Reality ARToolKit 基于Marker的AR库 ARToolKitPlus ARToolKit的增强版。实现了更好的姿态估计算法。 PTAM 实时的跟踪、SLAM、AR库。无需Marker,模板,内置传感器等。 BazAR 基于特征点检测和识别的AR库。 局部不变特征/Local Invariant Feature VLFeat 目前最好的Sift开源实现。同时包含了KD-tree,KD-Forest,BoW实现。 Ferns 基于Naive Bayesian Bundle的特征点识别。高速,但占用内存高。 SIFT By Rob Hess 基于OpenCV的Sift实现。 目标检测/Object Detection AdaBoost By JianXin.Wu 又一个AdaBoost实现。训练速度快。 行人检测 By JianXin.Wu 基于Centrist和Linear SVM的快速行人检测。 (近似)最近邻/ANN FLANN 目前最完整的(近似)最近邻开源库。不但实现了一系列查找算法,还包含了一种自动选取最快算法的机制。 ANN 另外一个近似最近邻库。 SLAM & SFM SceneLib [LGPL] monoSLAM库。由Androw Davison开发。 图像分割/Segmentation SLIC Super Pixel 使用Simple Linear Iterative Clustering产生指定数目,近似均匀分布的Super Pixel。 目标跟踪/Tracking TLD 基于Online Random Forest的目标跟踪算法。 KLT Kanade-Lucas-Tracker Online boosting trackers Online Boosting Trackers 直线检测/Line Detection DSCC 基于联通域连接的直线检测算法。 LSD [GPL] 基于梯度的,局部直线段检测算子。 指纹/Finger Print pHash [GPL] 基于感知的多媒体文件Hash算法。(提取,对比图像、视频、音频的指纹) 视觉显著性/Visual Salience Global Contrast Based Salient Region Detection Ming-Ming Cheng的视觉显著性算法。 FFT/DWT FFTW [GPL] 最快,最好的开源FFT。 FFTReal [WTFPL] 轻量级的FFT实现。许可证是亮点。 音频处理/Audio processing STK [Free] 音频处理,音频合成。 libsndfile [LGPL] 音频文件IO。 libsamplerate [GPL ] 音频重采样。 小波变换 快速小波变换(FWT) FWT BRIEF: Binary Robust Independent Elementary Feature 一个很好的局部特征描述子,里面有FAST corner + BRIEF实现特征点匹配的DEMO:http://cvlab.epfl.ch/software/brief/ http://code.google.com/p/javacv Java打包的OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, and ARToolKitPlus库。可以放在Android上用~ libHIK,HIK SVM,计算HIK SVM跟Centrist的Lib。http://c2inet.sce.ntu.edu.sg/Jianxin/projects/libHIK/libHIK.htm 一组视觉显著性检测代码的链接:http://cg.cs.tsinghua.edu.cn/people/~cmm/saliency/ 介绍n款计算机视觉库/人脸识别开源库/软件 计算机视觉库 OpenCV OpenCV是Intel®开源计算机视觉库。它由一系列 C 函数和少量 C++ 类构成,实现了图像处理和计算机视觉方面的很多通用算法。 OpenCV 拥有包括 300 多个C函数的跨平台的中、高层 API。它不依赖于其它的外部库——尽管也可以使用某些外部库。 OpenCV 对非商业... 人脸识别 faceservice.cgi faceservice.cgi 是一个用来进行人脸识别的 CGI 程序, 你可以通过上传图像,然后该程序即告诉你人脸的大概坐标位置。faceservice是采用 OpenCV 库进行开发的。 OpenCV的.NET版 OpenCVDotNet OpenCVDotNet 是一个 .NET 对 OpenCV 包的封装。 人脸检测算法 jViolajones jViolajones是人脸检测算法Viola-Jones的一个Java实现,并能够加载OpenCV XML文件。 示例代码:http://www.oschina.net/code/snippet_12_2033 Java视觉处理库 JavaCV JavaCV 提供了在计算机视觉领域的封装库,包括:OpenCV、ARToolKitPlus、libdc1394 2.x 、PGR FlyCapture和FFmpeg。此外,该工具可以很容易地使用Java平台的功能。 JavaCV还带有硬件加速的全屏幕图像显示(CanvasFrame),易于在多个内核中执行并行代码(并... 运动检测程序 QMotion QMotion 是一个采用 OpenCV 开发的运动检测程序,基于 QT。 视频监控系统 OpenVSS OpenVSS - 开放平台的视频监控系统 - 是一个系统级别的视频监控软件视频分析框架(VAF)的视频分析与检索和播放服务,记录和索引技术。它被设计成插件式的支持多摄像头平台,多分析仪模块(OpenCV的集成),以及多核心架构。 手势识别 hand-gesture-detection 手势识别,用OpenCV实现 人脸检测识别 mcvai-tracking 提供人脸检测、识别与检测特定人脸的功能,示例代码 cvReleaseImage( &gray ); cvReleaseMemStorage(&storage); cvReleaseHaarClassifierCascade(&cascade);... 人脸检测与跟踪库 asmlibrary Active Shape Model Library (ASMLibrary©) SDK, 用OpenCV开发,用于人脸检测与跟踪。 Lua视觉开发库 libecv ECV 是 lua 的计算机视觉开发库(目前只提供linux支持) OpenCV的.Net封装 OpenCVSharp OpenCVSharp 是一个OpenCV的.Net wrapper,应用最新的OpenCV库开发,使用习惯比EmguCV更接近原始的OpenCV,有详细的使用样例供参考。 3D视觉库 fvision2010 基于OpenCV构建的图像处理和3D视觉库。 示例代码: ImageSequenceReaderFactory factory; ImageSequenceReader* reader = factory.pathRegex("c:/a/im_d.jpg", 0, 20); //ImageSequenceReader* reader = factory.avi("a.avi"); if (reader == NULL) { ... 基于QT的计算机视觉库 QVision 基于 QT 的面向对象的多平台计算机视觉库。可以方便的创建图形化应用程序,算法库主要从 OpenCV,GSL,CGAL,IPP,Octave 等高性能库借鉴而来。 图像特征提取 cvBlob cvBlob 是计算机视觉应用中在二值图像里寻找连通域的库.能够执行连通域分析与特征提取. 实时图像/视频处理滤波开发包 GShow GShow is a real-time image/video processing filter development kit. It successfully integrates DirectX11 with DirectShow framework. So it has the following features: GShow 是实时 图像/视频 处理滤波开发包,集成DiretX11。... 视频捕获 API VideoMan VideoMan 提供一组视频捕获 API 。支持多种视频流同时输入(视频传输线、USB摄像头和视频文件等)。能利用 OpenGL 对输入进行处理,方便的与 OpenCV,CUDA 等集成开发计算机视觉系统。 开放模式识别项目 OpenPR Pattern Recognition project(开放模式识别项目),致力于开发出一套包含图像处理、计算机视觉、自然语言处理、模式识别、机器学习和相关领域算法的函数库。 OpenCV的Python封装 pyopencv OpenCV的Python封装,主要特性包括: 提供与OpenCV 2.x中最新的C++接口极为相似的Python接口,并且包括C++中不包括的C接口 提供对OpenCV 2.x中所有主要部件的绑定:CxCORE (almost complete), CxFLANN (complete), Cv (complete), CvAux (C++ part almost... 视觉快速开发平台 qcv 计算机视觉快速开发平台,提供测试框架,使开发者可以专注于算法研究。 图像捕获 libv4l2cam 对函数库v412的封装,从网络摄像头等硬件获得图像数据,支持YUYV裸数据输出和BGR24的OpenCV IplImage输出 计算机视觉算法 OpenVIDIA OpenVIDIA projects implement computer vision algorithms running on on graphics hardware such as single or multiple graphics processing units(GPUs) using OpenGL, Cg and CUDA-C. Some samples will soon support OpenCL and Direct Compute API'... 高斯模型点集配准算法 gmmreg 实现了基于混合高斯模型的点集配准算法,该算法描述在论文: A Robust Algorithm for Point Set Registration Using Mixture of Gaussians, Bing Jian and Baba C. Vemuri. ,实现了C++/Matlab/Python接口... 模式识别和视觉库 RAVL Recognition And Vision Library (RAVL) 是一个通用 C++ 库,包含计算机视觉、模式识别等模块。 图像处理和计算机视觉常用算法库 LTI-Lib LTI-Lib 是一个包含图像处理和计算机视觉常用算法和数据结构的面向对象库,提供 Windows 下的 VC 版本和 Linux 下的 gcc 版本,主要包含以下几方面内容: 1、线性代数 2、聚类分析 3、图像处理 4、可视化和绘图工具 OpenCV优化 opencv-dsp-acceleration 优化了OpenCV库在DSP上的速度。 C++计算机视觉库 Integrating Vision Toolkit Integrating Vision Toolkit (IVT) 是一个强大而迅速的C++计算机视觉库,拥有易用的接口和面向对象的架构,并且含有自己的一套跨平台GUI组件,另外可以选择集成OpenCV 计算机视觉和机器人技术的工具包 EGT The Epipolar Geometry Toolbox (EGT) is a toolbox designed for Matlab (by Mathworks Inc.). EGT provides a wide set of functions to approach computer vision and robotics problems with single and multiple views, and with different vision se... OpenCV的扩展库 ImageNets ImageNets 是对OpenCV 的扩展,提供对机器人视觉算法方面友好的支持,使用Nokia的QT编写界面。 libvideogfx 视频处理、计算机视觉和计算机图形学的快速开发库。 Matlab计算机视觉包 mVision Matlab 的计算机视觉包,包含用于观察结果的 GUI 组件,貌似也停止开发了,拿来做学习用挺不错的。 Scilab的计算机视觉库 SIP SIP 是 Scilab(一种免费的类Matlab编程环境)的图像处理和计算机视觉库。SIP 可以读写 JPEG/PNG/BMP 格式的图片。具备图像滤波、分割、边缘检测、形态学处理和形状分析等功能。 STAIR Vision Library STAIR Vision Library (SVL) 最初是为支持斯坦福智能机器人设计的,提供对计算机视觉、机器学习和概率统计模 几种图像处理类库的比较 作者:王先荣 原文;http://www.cnblogs.com/xrwang/archive/2010/01/26/TheComparisonOfImageProcessingLibraries.html 前言 近期需要做一些图像处理方面的学习和研究,首要任务就是选择一套合适的图像处理类库。目前较知名且功能完善的图像处理类库有OpenCv、EmguCv、AForge.net等等。本文将从许可协议、下载、安装、文档资料、易用性、性能等方面对这些类库进行比较,然后给出选择建议,当然也包括我自己的选择。 许可协议 类库 许可协议 许可协议网址 大致介绍 OpenCv BSD www.opensource.org/licenses/bsd-license.html 在保留原来BSD协议声明的前提下,随便怎么用都行 EmguCv GPL v3 http://www.gnu.org/licenses/gpl-3.0.txt 你的产品必须也使用GPL协议,开源且免费 商业授权 http://www.emgu.com/wiki/files/CommercialLicense.txt 给钱之后可以用于闭源的商业产品 AForge.net LGPL v3 http://www.gnu.org/licenses/lgpl.html 如果不修改类库源代码,引用该类库的产品可以闭源和(或)收费 以上三种类库都可以用于开发商业产品,但是EmguCv需要付费;因为我只是用来学习和研究,所以这些许可协议对我无所谓。不过鉴于我们身在中国,如果脸皮厚点,去他丫的许可协议。 下载 可以很方便的下载到这些类库,下载地址分别为: 类库 下载地址 OpenCv http://sourceforge.net/projects/opencvlibrary/files/ EmguCv http://www.emgu.com/wiki/index.php/Download_And_Installation AForge.net http://www.aforgenet.com/framework/downloads.html 安装 这些类库的安装都比较简单,直接运行安装程序,并点“下一步”即可完成。但是OpenCv在安装完之后还需要一些额外的处理才能在VS2008里面使用,在http://www.opencv.org.cn有一篇名为《VC2008 Express下安装OpenCv 2.0》的文章专门介绍了如何安装OpenCv。 类库 安装难易度 备注 OpenCv 比较容易 VC下使用需要重新编译 EmguCv 容易 AForge.net 容易 相信看这篇文章的人都不会被安装困扰。 文档资料 类库 总体评价 书籍 网站 文档 示例 社区 备注 OpenCv 中等 中英文 中英文 中英文 较多 中文论坛 有中文资料但不完整 EmguCv 少 无 英文 英文 少 英文论坛 论坛人气很差 AForge.net 少 无 英文 英文 少 英文论坛 论坛人气很差 OpenCv有一些中文资料,另外两种的资料全是英文的;不过EmguCv建立在OpenCv的基础上,大部分OpenCv的资料可以用于EmguCv;而AForge.net是原生的.net类库,对GDI+有很多扩展,一些MSDN的资料可以借鉴。如果在查词典的基础上还看不懂英文文档,基本上可以放弃使用这些类库了。 易用性 易用性这玩意,主观意志和个人能力对它影响很大,下面是我的看法: 类库 易用性 备注 OpenCv 比较差 OpenCv大多数功能都以C风格函数形式提供,少部分功能以C++类提供。注意:2.0版将更多的功能封装成类了。 EmguCv 比较好 将OpenCv的绝大部分功能都包装成了.net类、结构或者枚举。不过文档不全,还是得对照OpenCv的文档去看才行。 AForge.net 好 纯.net类库,用起来很方便。 最近几年一直用的是C# ,把C和C++忘记得差不多了,况且本来C/C++我就不太熟,所以对OpenCv的看法恐怕有偏见。 视觉相关网站 这段时间因为项目的需要,我一直在折腾计算机视觉,尤其是双目立体视觉,代码、论文、工具箱等……占用了我几乎90%的工作时间,还在一点点地摸索,但进度实在不敢恭维,稍后我会把情况作个总结。 今天的主要任务就是和大家分享一些鄙人收藏的认为相当研究价值的网页: Oxford大牛:Andrew Zisserman,http://www.robots.ox.ac.uk/~vgg/hzbook/code/,此人主要研究多幅图像的几何学,该网站提供了部分工具,相当实用,还有例子 西澳大利亚大学的Peter Kovesi:http://www.csse.uwa.edu.au/~pk/research/matlabfns/,提供了一些基本的matlab工具,主要内容涉及Computer Vision, Image Processing CMU:http://www.cs.cmu.edu/afs/cs/project/cil/ftp/html/vision.html,该网站是我的最爱,尤其后面这个地址http://www.cs.cmu.edu/afs/cs/project/cil/ftp/html/v-groups.html,在这里提供了世界各地机构、大学在Computer Vision所涉及各领域的研究情况,包括Image Processing, Machine Vision,我后来也是通过它连接到了很多国外的网站 Cambridge:http://mi.eng.cam.ac.uk/milab.html,这是剑桥大学的机器智能实验室,里面有三个小组,Computer Vision & Robotics, Machine Intelligence, Speech,目前为止,Computer Vision & Robotics的一些研究成果对我日后的帮助可能会比较大,所以在此提及 大量计算机视觉方面的原版电子书:http://homepages.inf.ed.ac.uk/rbf/CVonline/books.htm,我今天先下了本Zisserman的书,呵呵,国外的原版书,虽然都是比较老的,但是对于基础的理解学习还是很有帮助的,至于目前的研究现状只能通过论文或者一些研究小组的网站 stanford:http://ai.stanford.edu/~asaxena/reconstruction3d/,这个网站是Andrew N.G老师和一个印度阿三的博士一起维护的,主要对于单张照片的三维重建,尤其他有个网页make3d.stanford.edu可以让你自己上传你的照片,通过网站来重建三维模型,这个网站对于刚开始接触Computer Vision的我来说,如获至宝,但有个致命问题就是make3d已经无法注册,我也多次给Andrew和印度阿三email,至今未回,郁闷,要是有这个网站的帐号,那还是相当爽的,不知道是不是由于他们的邮箱把我的email当成垃圾邮件过滤,哎,但这个stanford网站的贡献主要是代码,有很多computer vision的基础工具,貌似40M左右,全都是基于matlab的 caltech:http://www.vision.caltech.edu/bouguetj/calib_doc/,这是我们Computer Vision老师课件上的连接,主要是用于摄像机标定的工具集,当然也有涉及对标定图像三维重建的前期处理过程 JP Tarel:http://perso.lcpc.fr/tarel.jean-philippe/,这是他的个人主页,也是目前为止我发的email中,唯一一个给我回信的老外,因为我需要重建练习的正是他的图片集,我读过他的论文,但没有涉及代码的内容,再加上又是94年以前的论文,很多相关的引文,我都无法下载,在我的再三追问下,Tarel教授只告诉我,你可以按照我的那篇论文对足球进行重建,可是...你知道吗,你有很多图像处理的引文都下不了了,我只知道你通过那篇文章做了图像的预处理,根本不知道具体过程,当然我有幸找到过一篇90左右的论文,讲的是region-based segmentation,可是这文章里所有引文又是找不到的....悲剧的人生 开源软件网站:www.sourceforge.net 最后就是我们工大的Computer Vision大牛:sychen.com,我们Computer Vision课的老师,谦虚、低调,很有学者风范 总结:目前为止,我的个人感觉就是国外学者的论文包括刊登的资料大部分都是对原理进行的说明,并不是很在意具体的代码实现的讲解,而我却过分的关注于代码的实现,忽视Computer Vision的原理,国外学者对与自己相关领域的研究现状了解相当充分,对自己的工作进度更新也很勤快,很多好的网站我并没有完全列出来,在这里只是提了主要的几个,在这方面,我们国内的研究氛围有所不及,当然我选择的一些网站可能更多的是个人小组的研究介绍,不像一些专门从事领域研究的机构,会有那么多的权威资料,国外的网站有个很好的地方,就是有很多的免费资源,免费的matlab或者openCV工具集,免费的论文下载,课件下载等等,在这方面国内对于研究资源的共享,做得又有所差距,同样,国外的研究工具很多样,主要是matlab,一些发布的demo都使用C++写的,不过今天看到一个西班牙的研究机构(university of las palmas)用了个XMW的软件平台来实现图片的三维重建,data用的是人脸,而且国外的很多源代码基本上是在linux平台下完成的,对于我来说又是不方便,哎,可能要考虑装VM Ware了,不然双系统太累..... 目前,Computer Vision是全世界范围内自动化、计算机、数学领域的研究热点,综合性高,应用于医疗、军事、民用等等领域,其中有突出成绩的还是一下几所学校(个人见解):Cambridge(UK), Oxford(UK), CMU(US),Stanford(US),MIT(US),U.C.Berkeley(US),而UK的两所老牌高校,他们的实际应用领域丝毫不逊于stanford和CMU....
在入门级别的ObjC 教程中,我们常对从C++或Java 或其他面向对象语言转过来的程序员说,ObjC 中的方法调用(ObjC中的术语为消息)跟其他语言中的方法调用差不多,只是形式有些不同而已。 譬如C++ 中的: Bird * aBird = new Bird(); aBird->fly(); 在ObjC 中则如下: Bird * aBird = [[Bird alloc] init]; [aBird fly]; 初看起来,好像只是书写形式不同而已,实则差异大矣。C++中的方法调用可能是动态的,也可能是静态的;而ObjC中的消息都为动态的。下文将详细介绍为什么是动态的,以及编译器在这背后做了些什么事情。 要说清楚消息这个话题,我们必须先来了解三个概念Class, SEL, IMP,它们在objc/objc.h 中定义: typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id; typedef struct objc_selector *SEL; typedef id (*IMP)(id, SEL, ...); Class 的含义 Class 被定义为一个指向 objc_class的结构体指针,这个结构体表示每一个类的类结构。而 objc_class 在objc/objc_class.h中定义如下: struct objc_class { struct objc_class * isa; struct objc_class * super_class; /*父类*/ const char *name; /*类名字*/ long version; /*版本信息*/ long info; /*类信息*/ long instance_size; /*实例大小*/ struct objc_ivar_list *ivars; /*实例参数链表*/ struct objc_method_list **methodLists; /*方法链表*/ struct objc_cache *cache; /*方法缓存*/ struct objc_protocol_list *protocols; /*协议链表*/ }; 由此可见,Class 是指向类结构体的指针,该类结构体含有一个指向其父类类结构的指针,该类方法的链表,该类方法的缓存以及其他必要信息。 NSObject 的class 方法就返回这样一个指向其类结构的指针。每一个类实例对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。通过该指针,对象可以访问它对应的类以及相应的父类。如图一所示: 如图一所示,圆形所代表的实例对象的第一个实例变量为 isa,它指向该类的类结构 The object’s class。而该类结构有一个指向其父类类结构的指针superclass, 以及自身消息名称(selector)/实现地址(address)的方法链表。 方法的含义: 注意这里所说的方法链表里面存储的是Method 类型的。图一中selector 就是指 Method的 SEL, address就是指Method的 IMP。 Method 在头文件 objc_class.h中定义如下: typedef struct objc_method *Method; typedef struct objc_ method { SEL method_name; char *method_types; IMP method_imp; }; 一个方法 Method,其包含一个方法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法的具体实现的函数指针。 SEL 的含义: 在前面我们看到方法选标 SEL 的定义为: typedef struct objc_selector *SEL; 它是一个指向 objc_selector 指针,表示方法的名字/签名。如下所示,打印出 selector。 -(NSInteger)maxIn:(NSInteger)a theOther:(NSInteger)b { return (a > b) ? a : b; } NSLog(@"SEL=%s", @selector(maxIn:theOther:)); 输出:SEL=maxIn:theOther: 不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。 IMP 的含义: 在前面我们也看到 IMP 的定义为: typedef id (*IMP)(id, SEL, ...); 根据前面id 的定义,我们知道 id是一个指向 objc_object 结构体的指针,该结构体只有一个成员isa,所以任何继承自 NSObject 的类对象都可以用id 来指代,因为 NSObject 的第一个成员实例就是isa。 至此,我们就很清楚地知道 IMP 的含义:IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。 NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。 下面的例子展示了怎么使用指针来调用setFilled:的方法实现: void (*setter)(id, SEL, BOOL); int i; setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)]; for (i = 0; i < 1000; i++) setter(targetList[i], @selector(setFilled:), YES); 使用methodForSelector:来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重复发送很多次时才有意义,例如上面的for循环。 注意,methodForSelector:是Cocoa运行时系统的提供的功能,而不是Objective-C语言本身的功能。 消息调用过程: 至此我们对ObjC 中的消息应该有个大致思路了:示例 Bird * aBird = [[Bird alloc] init]; [aBird fly]; 中对 fly 的调用,编译器通过插入一些代码,将之转换为对方法具体实现IMP的调用,这个 IMP是通过在 Bird 的类结构中的方法链表中查找名称为fly 的 选标SEL 对应的具体方法实现找到的。 上面的思路还有一些没有提及的话题,比如说编译器插入了什么代码,如果在方法链表中没有找到对应的 IMP又会如何,这些话题在下面展开。 消息函数 obj_msgSend: 编译器会将消息转换为对消息函数 objc_msgSend的调用,该函数有两个主要的参数:消息接收者id 和消息对应的方法选标 SEL, 同时接收消息中的任意参数: id objc_msgSend(id theReceiver, SELtheSelector, ...) 如上面的消息 [aBird fly]会被转换为如下形式的函数调用: objc_msgSend(aBird, @selector(fly)); 该消息函数做了动态绑定所需要的一切工作: 1,它首先找到 SEL 对应的方法实现 IMP。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。2, 然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传递给方法实现 IMP。3, 最后,将方法实现的返回值作为该函数的返回值返回。 编译器会自动插入调用该消息函数objc_msgSend的代码,我们无须在代码中显示调用该消息函数。当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:消息的接收者以及方法名称 SEL。这些参数帮助方法实现获得了消息表达式的信息。它们被认为是”隐藏“的是因为它们并没有在定义方法的源代码中声明,而是在代码编译时是插入方法的实现中的。 尽管这些参数没有被显示声明,但在源代码中仍然可以引用它们(就象可以引用消息接收者对象的实例变量一样)。在方法中可以通过self来引用消息接收者对象,通过选标_cmd来引用方法本身。在下面的例子中,_cmd 指的是strange方法,self指的收到strange消息的对象。 - strange { id target = getTheReceiver(); SEL method = getTheMethod(); if (target == self || mothod == _cmd) return nil; return [target performSelector:method]; } 在这两个参数中,self更有用一些。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。 查找 IMP 的过程: 前面说了,objc_msgSend 会根据方法选标 SEL 在类结构的方法列表中查找方法实现IMP。这里头有一些文章,我们在前面的类结构中也看到有一个叫objc_cache *cache 的成员,这个缓存为提高效率而存在的。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。。 查找IMP 时: 1,首先去该类的方法 cache 中查找,如果找到了就返回它; 2,如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。3,3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中。 4,如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则进入下文中要讲的消息转发流程。 便利函数: 我们可以通过NSObject的一些方法获取运行时信息或动态执行一些消息: class 返回对象的类; isKindOfClass 和 isMemberOfClass检查对象是否在指定的类继承体系中; respondsToSelector 检查对象能否相应指定的消息; conformsToProtocol 检查对象是否实现了指定协议类的方法; methodForSelector 返回指定方法实现的地址。 performSelector:withObject 执行SEL 所指代的方法。 消息转发: 通常,给一个对象发送它不能处理的消息会得到出错提示,然而,Objective-C运行时系统在抛出错误之前,会给消息接收对象发送一条特别的消息forwardInvocation 来通知该对象,该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数。我们可以实现forwardInvocation:方法来对不能处理的消息做一些默认的处理,也可以将消息转发给其他对象来处理,而不抛出错误。 关于消息转发的作用,可以考虑如下情景:假设,我们需要设计一个能够响应negotiate消息的对象,并且能够包括其它类型的对象对消息的响应。 通过在negotiate方法的实现中将negotiate消息转发给其它的对象来很容易的达到这一目的。 更进一步,假设我们希望我们的对象和另外一个类的对象对negotiate的消息的响应完全一致。一种可能的方式就是让我们的类继承其它类的方法实现。 然后,有时候这种方式不可行,因为我们的类和其它类可能需要在不同的继承体系中响应negotiate消息。 虽然我们的类无法继承其它类的negotiate方法,但我们仍然可以提供一个方法实现,这个方法实现只是简单的将negotiate消息转发给其他类的对象,就好像从其它类那儿“借”来的现一样。如下所示: - negotiate { if ([someOtherObject respondsToSelector:@selector(negotiate)]) return [someOtherObject negotiate]; return self; } 这种方式显得有欠灵活,特别是有很多消息都希望传递给其它对象时,我们就必须为每一种消息提供方法实现。此外,这种方式不能处理未知的消息。当我们写下代码时,所有我们需要转发的消息的集合都必须确定。然而,实际上,这个集合会随着运行时事件的发生,新方法或者新类的定义而变化。 forwardInvocation:消息给这个问题提供了一个更特别的,动态的解决方案:当一个对象由于没有相应的方法实现而无法响应某消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都从NSObject类中继承了forwardInvocation:方法。然而,NSObject中的方法实现只是简单地调用了doesNotRecognizeSelector:。通过实现我们自己的forwardInvocation:方法,我们可以在该方法实现中将消息转发给其它对象。 要转发消息给其它对象,forwardInvocation:方法所必须做的有: 1,决定将消息转发给谁,并且 2,将消息和原来的参数一块转发出去。 消息可以通过invokeWithTarget:方法来转发: - (void) forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector:[anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; } 转发消息后的返回值将返回给原来的消息发送者。您可以将返回任何类型的返回值,包括: id,结构体,浮点数等。 forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的"吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。 注意: forwardInvocation:方法只有在消息接收对象中无法正常响应消息时才会被调用。 所以,如果我们希望一个对象将negotiate消息转发给其它对象,则这个对象不能有negotiate方法。否则,forwardInvocation:将不可能会被调用。 消息转发示例: // Proxy @interface Proxy : NSObject -(void)MissMethod; @end @implementation Proxy -(void)MissMethod { NSLog(@" >> MissMethod() in Proxy."); } @end // Foo @interface Foo : NSObject @end @implementation Foo - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL name = [anInvocation selector]; NSLog(@" >> forwardInvocation for selector [%@]", NSStringFromSelector(name)); Proxy * proxy = [[[Proxy alloc] init] autorelease]; if ([proxy respondsToSelector:name]) { [anInvocation invokeWithTarget:proxy]; } else { [super forwardInvocation:anInvocation]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [Proxy instanceMethodSignatureForSelector:aSelector]; } @end // 调用代码 Foo * foo = [[[Foo alloc] init] autorelease]; [foo MissMethod]; 运行上面调用代码将会输出: >> forwardInvocation MissMethod >> MissMethod() in Proxy.
前言: Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增加方法,属性等等,本文对相关的几个要点做了一个小结。 (1)运行时对函数进行动态替换 : class_replaceMethod 使用该函数可以在运行时动态替换某个类的函数实现,这样做有什么用呢?最起码,可以实现类似windows上hook效果,即截获系统类的某个实例函数,然后塞一些自己的东西进去,比如打个log什么的。 示例代码: IMP orginIMP; NSString * MyUppercaseString(id SELF, SEL _cmd) { NSLog(@"begin uppercaseString"); NSString *str = orginIMP (SELF, _cmd);(3) NSLog(@"end uppercaseString"); return str; } -(void)testReplaceMethod { Class strcls = [NSString class]; SEL oriUppercaseString = @selector(uppercaseString); orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString]; (1) IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2) NSString *s = "hello world"; NSLog(@"%@",[s uppercaseString]]; } 执行结果为: begin uppercaseString end uppercaseString HELLO WORLD 这段代码的作用就是 (1)得到uppercaseString这个函数的函数指针存到变量orginIMP中 (2)将NSString类中的uppercaseString函数的实现替换为自己定义的MyUppercaseString (3)在MyUppercaseString中,先执行了自己的log代码,然后再调用之前保存的uppercaseString的系统实现,这样就在系统函数执行之前加入自己的东西,后面每次对NSString调用uppercaseString的时候,都会打印出log来 与class_replaceMethod相仿,class_addMethod可以在运行时为类增加一个函数。 (2)当某个对象不能接受某个selector时,将对该selector的 调用转发给另一个对象: - (id)forwardingTargetForSelector:(SEL)aSelector forwardingTargetForSelector是NSObject的函数,用户可以在派生类中对其重载,从而将无法处理的selector转发给另一个对象。还是以上面的uppercaseString为例,如果用户自己定义的CA类的对象a,没有uppercaseString这样一个实例函数,那么在不调用respondSelector的情况下,直接执行[a performSelector:@selector"uppercaseString"],那么执行时一定会crash,此时,如果CA实现了forwardingTargetForSelector函数,并返回一个NSString对象,那么就相对于对该NSString对象执行了uppercaseString函数,此时就不会crash了。当然实现这个函数的目的并不仅仅是为了程序不crash那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。 示例代码: 1 @interface CA : NSObject 3 -(void)f; 4 5 @end 6 7 @implementation CA 8 9 - (id)forwardingTargetForSelector:(SEL)aSelector 11 { 13 if (aSelector == @selector(uppercaseString)) 15 { 17 return@"hello world"; 19 } 21 } 测试代码: CA *a = [CA new]; NSString * s = [a performSelector:@selector(uppercaseString)]; NSLog(@"%@",s); 测试代码的输出为:HELLO WORLD ps:这里有个问题,CA类的对象不能接收@selector(uppercaseString),那么如果我在forwardingTargetForSelector函数中用class_addMethod给CA类增加一个uppercaseString函数,然后返回self,可行吗?经过试验,这样会crash,此时CA类其实已经有了uppercaseString函数,但是不知道为什么不能调用,如果此时new一个CA类的对象,并返回,是可以成功的。 (3)当某个对象不能接受某个selector时,向对象所属的类动态 添加所需的selector: + (BOOL) resolveInstanceMethod:(SEL)aSEL 这个函数与forwardingTargetForSelector类似,都会在对象不能接受某个selector时触发,执行起来略有差别。前者的目的主要在于给客户一个机会来向该对象添加所需的selector,后者的目的在于允许用户将selector转发给另一个对象。另外触发时机也不完全一样,该函数是个类函数,在程序刚启动,界面尚未显示出时,就会被调用。 在类不能处理某个selector的情况下,如果类重载了该函数,并使用class_addMethod添加了相应的selector,并返回YES,那么后面forwardingTargetForSelector就不会被调用,如果在该函数中没有添加相应的selector,那么不管返回什么,后面都会继续调用forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受该selector的对象,那么resolveInstanceMethod会再次被触发,这一次,如果仍然不添加selector,程序就会报异常 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 代码示例一: 1 @implementation CA 3 void dynamicMethodIMP(id self, SEL _cmd) 5 { 7 printf("SEL %s did not exist\n",sel_getName(_cmd)); 9 } 10 11 + (BOOL) resolveInstanceMethod:(SEL)aSEL 13 { 15 if (aSEL == @selector(t)) 17 { 19 class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP, "v@:"); 21 return YES; 23 } 25 return [superresolveInstanceMethod:aSEL]; 27 } 28 29 @end 测试代码: CA * ca = [CA new] [ca performSelector:@selector(t)]; 执行结果 SEL t did not exist 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 示例代码二: @implementation CA void dynamicMethodIMP(id self, SEL _cmd) { printf("SEL %s did not exist\n",sel_getName(_cmd)); } + (BOOL) resolveInstanceMethod:(SEL)aSEL { return YES; } - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(uppercaseString)) { return @"hello world"; } } 测试代码 : a = [[CA alloc]init]; NSLog(@"%@",[a performSelector:@selector(uppercaseString)]; 该测试代码的输出为:HELLO WORLD 对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发 forwardingTargetForSelector函数,在forwardingTargetForSelector函数中,返回了一个NSString "hello world",因此会由该string来执行uppercaseString函数,最终返回大写的hello world。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 示例代码三: @implementation CA + (BOOL) resolveInstanceMethod:(SEL)aSEL { return YES; } - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; } 测试代码: 1 a = [[CA alloc]init]; 2 NSLog(@"%@",[a performSelector:@selector(uppercaseString)]; 这段代码的执行顺序为: (1):首先在程序刚执行,AppDelegate都还没有出来时,resolveInstanceMethod就被触发, (2)等测试代码执行时,forwardingTargetForSelector被调用 (3)由于forwardingTargetForSelector返回了nil,因此运行时还是找不到uppercaseString selector,这时又会触发resolveInstanceMethod,由于还是没有加入selector,于是会crash。 (4) 使用class_copyPropertyList及property_getName 获取类的属性列表及每个属性的名称 u_int count; objc_property_t* properties= class_copyPropertyList([UIView class], &count); for (int i = 0; i < count ; i++) { const char* propertyName = property_getName(properties[i]); NSString *strName = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]; NSLog(@"%@",strName); } 以上代码获取了UIView的所有属性并打印属性名称, 输出结果为: skipsSubviewEnumeration viewTraversalMark viewDelegate monitorsSubtree backgroundColorSystemColorName gesturesEnabled deliversTouchesForGesturesToSuperview userInteractionEnabled tag layer _boundsWidthVariable _boundsHeightVariable _minXVariable _minYVariable _internalConstraints _dependentConstraints _constraintsExceptingSubviewAutoresizingConstraints _shouldArchiveUIAppearanceTags (5) 使用class_copyMethodList获取类的所有方法列表 获取到的数据是一个Method数组,Method数据结构中包含了函数的名称、参数、返回值等信息,以下代码以获取名称为例: u_int count; Method* methods= class_copyMethodList([UIView class], &count); for (int i = 0; i < count ; i++) { SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; NSLog(@"%@",strName); } 代码执行后将输出UIView所有函数的名称,具体结果略。 其他一些相关函数: 1.SEL method_getName(Method m) 由Method得到SEL 2.IMP method_getImplementation(Method m) 由Method得到IMP函数指针 3.const char *method_getTypeEncoding(Method m) 由Method得到类型编码信息 4.unsigned int method_getNumberOfArguments(Method m)获取参数个数 5.char *method_copyReturnType(Method m) 得到返回值类型名称 6.IMP method_setImplementation(Method m, IMP imp) 为该方法设置一个新的实现 (6)总结 总而言之,使用runtime技术能做些什么事情呢? 可以在运行时,在不继承也不category的情况下,为各种类(包括系统的类)做很多操作,具体包括: 增加 增加函数:class_addMethod 增加实例变量:class_addIvar 增加属性:@dynamic标签,或者class_addMethod,因为属性其实就是由getter和setter函数组成 增加Protocol:class_addProtocol (说实话我真不知道动态增加一个protocol有什么用,-_-!!) 获取 获取函数列表及每个函数的信息(函数指针、函数名等等):class_getClassMethod method_getName ... 获取属性列表及每个属性的信息:class_copyPropertyList property_getName 获取类本身的信息,如类名等:class_getName class_getInstanceSize 获取变量列表及变量信息:class_copyIvarList 获取变量的值 替换 将实例替换成另一个类:object_setClass 将函数替换成一个函数实现:class_replaceMethod 直接通过char *格式的名称来修改变量的值,而不是通过变量 (7)运行时操作 - 方法交换 后面主要介绍oc类的运行时行为。这里面包括运行时方法的更换,消息的转发,以及动态属性。这些对于面向方面编程AOP的热爱者还是很有用的,当然还有很多其他的作用,例如可配置编程之类的。但是按照我之前在java和dotnet的编程经验,这些都是使用性能为代价的。所以尽量在程序开始部分完成操作,而不是用于程序行为的代码。 第一段代码是方法交换。下面的例子将使用自己的代码替换[NSString stringByAppendingPathComponent]方法的实现。 这里是替换代码: 1 2 3 4 5 6 7 8 9 10 11 NSString * NSStringstringByAppendingPathComponent(id SELF, SEL _cmd, NSString * path){ //开发文档推荐的是这种定义形式,其实在方法交换场景下,这是没有必要的,你甚至可以给一个().但是如果你要替换的方法实际并不存在,那么这个定义形式是必须的。 NSLog(@”this is a fake imp for method %@”, NSStringFromSelctor(_cmd)); NSLog(@”I won’t do anything! but I will return a virus!”);//疯狂医生的逻辑 return [NSString stringWithCString: “virus!!!” encoding:NSUTF8StringEncoding]; } 下面是方法交换的代码: Class strcls = [NSString class]; SEL oriStringByAppendingPathComponent = @selector(stringByAppendingPathComponent:); class_replaceMethod(strcls, oriStringByAppendingPathComponent, (IMP)NSStringstringByAppendingPathComponent, NULL); //后面的type参数可以是NULL。如果要替换的方法不存在,那么class_addMethod会被调用,type参数将被用来设置被添加的方法 /* Apple development reference 的描述如下: type参数:An array of characters that describe the types of the arguments to the method. For possible values, see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:” (the first character is the return type). If the method identified by name does not yet exist, it is added as if class_addMethod were called. The type encoding specified by types is used as given. If the method identified by name does exist, its IMP is replaced as if method_setImplementation were called. The type encoding specified by types is ignored. */ (8)运行时操作 - 动态属性 objective-c 2.0中增加了一个新的关键字@dynamic, 用于定义动态属性。所谓动态属性相对于@synthesis,不是由编译器自动生成setter或者getter,也不是由开发者自己写的setter或getter,而是在运行时动态添加的setter和getter。 一般我们定义一个属性都是类似以下方法: 1 2 3 4 5 6 7 8 @interface Car:NSObject; @property (retain) NSString* name; @end @implement Car; @synthesize name; @end 这种情况下,@synthesize关键字告诉编译器自动实现setter和getter。另外,如果不使用@synthesize,也可以自己实现getter或者setter 1 2 3 4 5 6 7 @implement Car; (NSString*) name{ return _name; } (void) setName:(NSString*) n{ _name = n; } 现在通过@dynamic,还可以通过第三种方法来实现name的setter和getter。实现动态属性,需要在代码中覆盖resolveInstanceMethod来动态的添加name的setter和getter。这个方法在每次找不到方法实现的时候都会被调用。事实上,NSObject的默认实现就是抛出异常。 参考以下代码: 下面是定义动态属性和实现动态属性的代码: 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 @interface Car:NSObject @property (retain) NSString* name; @end ---car.m (void) dynamicSetName(id SELF, SEL _cmd, NSString * n){ //这个定义形式是必须的。 //结合下面的类型描述字符v表示返回为void //@表示第一个参数id //:表示SEL //@表示参数n NSLog(@"the new name is going to send in:%@!", n); } @implement Car @dynamic name; -(BOOL) resolveInstanceMethod:(SEL) sel{ NSString * method = NSStringFromSelector(sel); if([method isEqualToString:@"setName:"]){ class_addMethod([selfclass], sel, (IMP)dynamicSetName, "v@:@");//类型描述字符,可以参考开发文档中有关@encode关键字的说明。 return YES: } return [super resolveInstanceMethod:sel]; } @end; 上面的代码动态实现了name的setter,当然这个时候如果想要调用car.name,就会抛出错误,因为我并没有实现它的getter。 (9)运行时操作 - 消息转发 第七篇中讲动态属性时,提到了resolveInstanceMethod,这个方法不仅在这里用,还用来实现消息的转发。 消息的转发就是向对象发送一个它本身并没有实现的消息,在运行时确定它实际产生的行为。 举个例子来说,一个Person对象,在运行时根据实际情况,决定是否响应fly这样的方法。如果条件具备,则fly被响应。否则,则不具备这样的方法。类似于AoP的做法。 要实现消息转发,需要覆盖三个方法: 1, resolveInstanceMethod(可选),这个方法为你提供了一个机会,在消息被发现本身没有在类中定义时你可以通过class_addMethod将它添加进去。如果你不这样做,不管你最终返回YES还是NO,程序都会jmp到下一个方法。 2, –(NSMethodSignature*)methodSignatureForSelector: (SEL) sel;通过覆盖这个方法,可以将你要转发的消息的签名创建并返回。如果在这里你返回nil,那么就会直接抛出异常。如果返回一个签名,则程序会jmp到第三个方法。这里返回的方法签名,必须满足两个条件之一(方法名相同||输入参数相同). 3, –(void)forwardInvocation:(NSInvocation * )anInvocation;在这里进行实际的方法调用。参考以下代码: #import <Foundation/Foundation.h> #import "Human.h" @interface Plane : NSObject -(void)fly:(Human*)p; -(int)fly1; -(void)fly2:(Human*)p withBird:(NSString*)birdName; @end #import "Plane.h" @implementation Plane -(void)fly:(Human*)p{ NSLog(@"Fly with a guy whose information is \"%@\"", [p description]); NSLog(@"fly......"); } -(int)fly1{ NSLog(@"I can fly in Plane for fly1"); return 0; } -(void)fly2:(Human*)p withBird:(NSString*)birdName{ NSLog(@"Fly with a guy whose information is \"%@\"", [p description]); NSLog(@"fly......bird:'%@'.", birdName); } @end #import <Foundation/Foundation.h>void dynamicMethod(id self, SEL _cmd, float height); @interface Human : NSObject{ float _height; } @property(retain, nonatomic) NSString * name; @property(readonly) float weight; @property float height; -(id) initWithWeight:(float)weight; -(NSString*)description; @end #import <objc/runtime.h> #import "Human.h" #import "Plane.h"void dynamicMethod(id self, SEL _cmd, float height){ NSLog(@"dynamicMethod:%@", NSStringFromSelector(_cmd)); // here is a question. how to implement the statements to assign the property of id(human.height); // looks like the dynamic property is just for linkage. // 当赋值发生时,由于动态函数的全局性,可以访问其他全局变量,对象。所以更像一种链接机制} @implementation Human @synthesize name; @synthesize weight; @dynamic height; -(id)initWithWeight:(float)w{ if(self=[super init]){ weight = w; } return self; } -(NSString*)description{ return [NSString stringWithFormat:@"The human whose name is \"%@\". weight is [%f]. height is [%f]", self.name, self.weight, self.height]; } -(float)height{ return _height; } -(void)fly2{ NSLog(@"yes I can fly in 2!"); } +(BOOL)resolveInstanceMethod:(SEL)sel{ NSString* method = NSStringFromSelector(sel); if([method isEqualToString:@"setHeight:"]){ class_addMethod([self class], sel, (IMP)dynamicMethod, "v@:f"); } return [super resolveInstanceMethod:sel]; } -(NSMethodSignature*)methodSignatureForSelector:(SEL)selector{ NSMethodSignature * signature = [super methodSignatureForSelector:selector]; if(!signature&&[NSStringFromSelector(selector) isEqualToString:@"fly"]){ //signature = [[self class] instanceMethodSignatureForSelector:@selector(fly2)]; SEL newSel = NSSelectorFromString(@"fly1"); //will ok. // SEL newSel = NSSelectorFromString(@"fly:");//will ok. // SEL newSel = NSSelectorFromString(@"fly2:withBird");//will wrong. //这里返回的消息,必须符合以下条件之一: //方法名相同 //输入参数相同 if([Plane instancesRespondToSelector:newSel]){ signature = [Plane instanceMethodSignatureForSelector:newSel]; } } return signature; } -(void)forwardInvocation:(NSInvocation *)anInvocation{ NSString * selName = NSStringFromSelector([anInvocation selector]); NSMethodSignature * sig = [anInvocation methodSignature]; NSLog(@"the signature %@", [NSString stringWithCString:[sig methodReturnType] encoding:NSUTF8StringEncoding]); if([selName isEqualToString:@"fly"]){ Plane * p = [[Plane alloc] init]; SEL newSel = NSSelectorFromString(@"fly:"); IMP imp = [p methodForSelector:newSel]; imp(p, newSel, self); [p release]; } } -(void)dealloc{ [self setName:nil]; [super dealloc]; } @end #import <Foundation/Foundation.h> #import <objc/runtime.h> #import "Human.h" #import "Plane.h"int main (int argc, const char * argv[]) { @autoreleasepool { // insert code here... Human * h = [[Human alloc] initWithWeight:17.3]; h.name=@"Chris"; h.height = 18.0; NSLog(@"%@", [h description]); [h fly]; [h release]; h=nil; }} (10)运行时操作 - 序列化 学习到目前为止,我看到oc实现的序列化方式有两种:NSKeyedArchiver,NSPropertyListSerialization。 在这两种序列化方式中,NSData都是序列化的目标。两种方式的不同点在于NSPropertyListSerialization只是针对字典类型的,而NSKeyedArchiver是针对对象的。(补充一下,在Mac OS环境下,还可以使用NSArchiver获得更加精简的二进制序列化内容,但是NSArchiver在iOS环境下不支持)。 首先讲NSPropertyListSerialization,这个相对简单,只要创建一个NSDictionary,然后调用NSPropertyListSerialization dataFromPropertyList, 将内容写入NSData中就可以了。如果要读入,就调用propertyListFromData. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 NSString * filepath = @”…”;//omitted. NSString * err;//不需要初始化。如果有错误发生,会被复制。 NSDictionary * props = [NSDictionary dictionaryWithObjectsAndKey:@”Lucy”, @"name”, @"Beijing, China”, @"city”, @"supervior”, @"position”, @"Qitiandasheng”, @"company”, nil]; NSData * data = [NSPropertyListSerialization dataFromPropertyList:props format:NSPropertyListXMLFormat_v1_0 errorDescription:&err]; if(!err){ [data writeToFile:filePath atomically:YES];//写入文件 }else{ NSLog(@"error with:%@", err); } 然后再来看NSKeyedArchiver。从基本的代码示例来看也很简单: 1 2 3 4 5 Person * lucy = [[Person alloc] initWithName:@"lucy"]; lucy.address = @"Beijing, China"; NSData * data = [NSKeyedArchiver archiveDataWithRootObject:lucy]; [data writeToFile:filePath]; 这里要求Person类必须事先了NSCoding协议。NSCoding协议中包含两个必须事先的方法initWithCoder和encodeWithCoder. 参考下面这两个方法的实现: 1 2 3 4 5 6 7 8 9 -(void)encodeWithCoder:(NSCoder *)aCoder{ [aCoder encodingObject:[NSNumber numberWithInt:self.age] forKey @"age"]; } -(id)initWithCoder:(NSCoder*) aDecoder{ if(self=[super init]){ self.age = (int)[(NSNumber*)[aDecoder decodeObjectForKey:@"age"] intValue]; } return self; } 这里之所以用int age作为序列化属性的示例,是为了引出另一个话题,就是在序列化时对基础类型的处理。NSCoder中是不能存储基础类型的,包括int,float,char *什么的。要将这些进行序列化,必须要进行封装。 一般封装有两种方式。对于数值基本类型,使用Cocoa提供的封装,NSNumber就可以了。对于定长的struct,要分情况,如果struct的所有类型都是定长,并且成员中没有struct类型,那么可以直接使用NSValue来封装;否则,需要自己想办法进行key-value编写。char * 应该直接使用NSString进行封装。
1.应用代码混淆,可参考国外开发者写的方案https://github.com/Polidea/ios-class-guard,这个还有点bug,需要懂一些shell脚本。2.链接一定要采用https,而且绑定证书,用afnetworking非常方便。3.关键的传输数据要进行数字信封(随机数加时间戳),数字摘要(md5),不对称加密(rsa)综合加固。4.关键业务的js通过加密的方式传回客户端,客户端解密再执行。5.对重要页面和功能进行埋点和添加时间戳,后台定义规则,发现异常的 功能调用 立即进行系统报警,引起报警的设备,IP,帐号进行异常处理。6.需要登录功能的一定要绑定设备,更换设备登陆的进行短信验证7.对越狱的设备进行提醒和限制某些功能的使用
软件目录路径:/Applications字体路径:System\Library\Fonts\CacheFIT皮肤:/Library/FIT文件夹:FIT漫画文件存放于:/private/var/mobile/Media/Photos/iComic (注:iComic目录需自己创建)或 /private/var/mobile/Documents/ 目录iPhoneVideoRecorder摄像软件目录路径:/private/var/mobile/Media/Videos点击下载。主题目录路径:/private/var/stash/Themes.BPznmT系统铃声目录路径:/System/Library/Audio/UISounds (wav铃声扩展名可以改为caf)电子书目录路径:/private/var/mobile/Media/EBooks短信铃声路径:/System/Library/Audio/UISounds文件名:sms-received1.caf至sms-received6.caf(caf是文件扩展名)拨号面板图标路径:/Applications/MobilePhone.app文件名:addcontact addcontact_pressed callbkgnd callbkgnd_pressed callglyph callglyph_big DefaultDialer delete delete_pressed MobilePhonePackedImages.artwork充电电池图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:BatteryBackground BatteryBG_1至BatteryBG_17天气补丁路径:/Applications/Weather.app文件名:Info手机信号图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:Default_0_Bars.png一直到Default_5_Bars.png 和FSO_0_Bars.png--FSO_5_Bars.png 10个图标为信号图标Wifi信号图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:Default_0_AirPort.png---Default_3_AirPort.png和FSO_0_AirPort.png---FSO_3_AirPort.png 8个图标为wifi信号图标Edge信号图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:Default_EDGE_ON.png和FSO_EDGE_ON.png 2图标为Edge信号图标解锁小图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:FSO_LockIcon.png待机播放器图标路径:/System/Library/CoreServices/SpringBoard.app 文件名:nexttrack.png , pause.png , play.png, prevtrack.png 4个图标为待机播放器图标IPOD播放信号图标路径:/System/Library/CoreServices/SpringBoard.app文件名:FSO_Play.png ,Default_Play.png闹钟信号图标路径:/System/Library/CoreServices/SpringBoard.app文件名:Default_AlarmClock.png ,FSO_AlarmClock.png震动图标路径:/System/Library/CoreServices/SpringBoard.app文件名:silent.png ,hud.png ,ring.png滑块图标路径:/System/Library/PrivateFrameworks/TelephonyUI.framework文件名:Bottombarknobgray.png(待机解锁滑块图标)bottombarknobgreen.png(待机状态下移动滑动来接听 滑块图标)Bottombarknobred.png(关机滑块 图标)bottombarbkgndlock(待机解锁滑块背景)bottombarlocktextmask(待机解锁滑条背景)解锁滑条图标路径:/System/Library/PrivateFrameworks/TelephonyUI.framework文件名:topbarbkgnd.png ,bottombarbkgndlock.png滑块文字路径:/System/Library/CoreServices/SpringBoard.app/zh_CN.lproj文件名:SpringBoard.strings待机时间字体路径:/System/Library/Fonts/Cache文件名:LockClock.ttf待机时间背景路径:/System/Library/Frameworks/UIKit.framework文件名:Other.artwork农历路径:/private/var/mobile/Library/Calendar文件名:Calendar.sqlitedb运营商图标路径:/System/Library/Carrier Bundles/Unknown.bundle文件名:Default_CARRIER_CHINAMOBILE--FSO_CARRIER_CHINAMOBILE(中国移动)Default_CARRIER_CHINAUNICOM--FSO_CARRIER_CHINAUNICOM(中国联通)系统瘦身路径:进入/System/Library/TextInput其中应该是管理各种语言输入的文件保留TextInput_en.bundle和TextInput_zh.bundle其余的全部删除,共有108M91安装软件目录:/private/var/stash/Applications.CA5uma===========================================iPhone里重要的目录路径有哪几个? 1. /private/var/mobile 新刷完的机器,要在这个文件夹下建一个Documents的目录,很多程序都要用到。 2. /private/var/mobile/Applications 通过AppStore和iTunes安装的程序都在里面。3. /private/var/stash 这个文件夹下的Applications目录里面是所有通过cydia和app安装的程序,Ringtones目录里是所有的手机铃音,自制铃音直接拷在里面即可,Themes目录里是所有Winterboard主题,可以手工修改。 4. /private/var/mobile/Media/ROMs/GBA gpsPhone模拟器存放rom的目录。 5. /private/var/mobile/Media/textReader textReader看书软件读取的电子书的存放路径。6. /Applications/WeDictPro.app或/Applications/WeDict.app(WeDict目录,WeDict字典放在该目录下,权限644不变) 7. /System/Library/Fonts/Cache(系统字体目录,要替换的字体放在该目录下,权限644不变) 8. /private/var/mobile/Media/Maps(离线地图目录,把地图文件夹放到该目录下,文件夹赋予777权限) 9. /private/var/mobile/Library/Downloads (ipa文件存放目录,把下载来的ipa文件放到此目录下,用Installous安装,后文会讲到) 10. /private/var/mobile/Library/Keyboard (系统拼音字库文件位置) 11. /private/var/stash/Themes.XXXXXX (winterboard主题文件存放路径) 12. /private/var/mobile/Media/DCIM/999APPLE (系统自带截屏文件存放路径,截屏方法:按住Power并快速按一下Home键) 13. /private/var/mobile/Media/Wikipedia/ (WIKI百科文件夹存放路径 14. /System/Library/Frameworks/UIKit.framework和/System/Library/PrivateFrameworks/AppSupport.framework(这两个都是电话号码显示规则文件存放路径,有关完美显示电话号码和短信号码_______________iPhone系统常用文件夹位置 电话界面/Applications/MobilePhone.app/1、【/Applications】 常用软件的安装目录 2. 【/private /var/ mobile/Media /iphone video Recorder】 iphone video Recorder录像文件存放目录 3、【/private /var/ mobile/Media /DCIM】相机拍摄的照片文件存放目录 4、【/private/var/ mobile /Media/iTunes_Control/Music】 iTunes上传的多媒体文件(例如MP3、MP4等)存放目录,文件没有被修改,但是文件名字被修改了,直接下载到电脑即可读取。 5、【/private /var/root/Media/EBooks】 熊猫看书存放目录 6、【/Library/Ringtones】 系统自带的来电铃声存放目录(用iTunes将文件转换为ACC文件,把后缀名改成.m4r,用iPhone_PC_Suite传到/Library/Ringtones即可) 7、【/System/Library/Audio/UISounds】 短信记其它系统默认效果铃声(m4r铃声文件改扩展名为.caf)短信铃声文件名为sms-received开头的caf文件 8、【/private/var/ mobile /Library/AddressBook】 系统电话本的存放目录。 9、【/private /var/ mobile/Media /iphone Recorder】 iphone Recorder录音软件文件存放目录 10、【/Applications/Preferences.app/zh_CN.lproj】 软件Preferences.app的中文汉化文件存放目录 11、【/Library/Wallpaper】 系统q1ang纸的存放目录 12、【/System/Library/Audio/UISounds】 系统声音文件的存放目录 13、【/private/var/root/Media/PXL】 ibrickr上传安装程序建立的一个数据库,估计和windows的uninstall记录差不多。 14、【/bin】 和linux系统差不多,是系统执行指令的存放目录。 15、【/private/var/ mobile /Library/SMS】 系统短信的存放目录 16、【/private/var/run】 系统进程运行的临时目录?(查看这里可以看到系统启动的所有进程) 17、【/private/var/logs/CrashReporter】 系统错误记录报 18.这个电池图标存放 用winterboard的,在自用的主题目录下,/var/stash/Themes.xxxxx/自用主题目录/Bundles/com.apple.springboard/目录内上传BatteryBG_1-17.png图片即可,如无com.apple.springboard目录,请自建。(Themes.xxxxx每个人都是不一样的,故用xxxxx表示) 不用的也可以直接替换/System/Library/CoreServices/SpringBoard.app 图标替换路径 WB相关主题 直接放在Library/Themes里面 注意修改权限 充电图标: System/Library/CoreServices/SpringBoard.app/BatteryBG_1.png 一直到 BatteryBG_17.png ,Batteryfill.png 18个图标为充电图标 手机信号图标: SystemLibraryCoreServicesSpringBoard.app下Default_0_Bars.png一直到Default_5_Bars.png 和FSO_0_Bars.png--FSO_5_Bars.png 10个图标为信号图标 Wifi信号图标: SystemLibraryCoreServicesSpringBoard.appDefault_0_AirPort.png---Default_3_AirPort.png和FSO_0_AirPort.png---FSO_3_AirPort.png 8个图标为wifi信号图标 Edge信号图标: SystemLibraryCoreServicesSpringBoard.app Default_EDGE_ON.png和FSO_EDGE_ON.png 2图标为Edge信号图标 日期美化图标: SystemLibraryCoreServicesSpringBoard.app|FSO_LockIcon.png 待机播放器图标: SystemLibraryCoreServicesSpringBoard.app|nexttrack.png , pause.png , play.png, prevtrack.png 4个图标为待机播放器图标 IPOD播放信号 SystemLibraryCoreServicesSpringBoard.appFSO_Play.png ,Default_Play.png 闹钟信号 SystemLibraryCoreServicesSpringBoard.appDefault_AlarmClock.png ,FSO_AlarmClock.png 震动图标 SystemLibraryCoreServicesSpringBoard.appsilent.png ,hud.png ,ring.png 滑块图标: SystemLibraryPrivateFrameworksTelephonyUI.framework 目录下 Bottombarknobgray.png(待机解锁滑块图标) bottombarknobgreen.png(待机状态下移动滑动来接听 滑块图标) Bottombarknobred.png(关机滑块 图标) 待机时间字体: /System/Library/Fonts/Cache/LockClock.ttf 待机时间背景: system/library/Frameworks/UIKit.framework/Other.artwork 滑块文字变为闪光字: SystemLibraryPrivateFrameworksTelephonyUI.framework/bottombarlocktextmask.png 解锁滑条路径: SystemLibraryPrivateFrameworksTelephonyUI.framework/ opbarbkgnd.png ,bottombarbkgndlock.png 移动:改彩色的文件名为:Default_CARRIER_CHINAMOBILE.png 改黑白的文件名为:FSO_CARRIER_CHINAMOBILE.png 联通:改彩色的文件名为:Default_CARRIER_CHINAUNICOM.png 改黑白的文件名为:FSO_CARRIER_CHINAUNICOM.png 滑块文字路径 /System/Library/CoreServices/SpringBoard.app/zh_CN.lproj 1. /private/var/mobile 新刷完的机器,要在这个文件夹下建一个Documents的目录。 2. /private/var/mobile/Applications 通过AppStore和iTunes安装的程序都在里面。 3. /private/var/stash 这个文件夹下的Applications目录里面是所有通过Cydia和app安装的程序,Ringtones目录里是所有的手机铃音,自制铃音直接拷在里面即可,Themes目录里是所有Winterboard主题,可以手工修改。 4. /var/mobile/Media/ROMs/GBA gpsPhone模拟器存放rom的目录。 5. /var/mobile/Media/textReader textReader看书软件读取的电子书的存放路径。 6. /System/Library/Fonts/Cache(系统字体目录,要替换的字体放在该目录下,权限644不变) 7. /private/var/mobile/Media/Maps(离线地图目录,把地图文件夹放到该目录下,文件夹赋予777权限) 8. /private/var/mobile/Library/Downloads (ipa文件存放目录,用Installous安装) 9. /private/var/mobile/Library/Keyboard (系统拼音字库文件位置) 10. /var/stash/Themes.XXXXXX (winterboard主题文件存放路径) 11. /private/var/mobile/Media/DCIM/999APPLE (系统自带截屏文件存放路径)CYDIA安装deb路径 /private/var/root/Media/Cydia/AutoInstall
OpenCV的变更日志 版本:3.1 2015年10月 这是在3.x系列第一稳定的更新。应当提醒的是,因为OpenCV的3.0,我们已经改变了版本枚举方案,使3.1是同一类更新到3.0为2.4.1是2.4.0。 〜 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 〜 已经有很多成功的项目,今年(学生和导师(S)的项目名称后列出),其结果可作为OpenCV的3.1的一部分(部分在主存储库中,但大多opencv_contrib): 全方位摄像机标定和立体三维重建 - opencv_contrib / ccalib模块(百盛丽,博力) 结构与运动 - opencv_contrib / SFM模块(埃德加·里巴,文森特RABAUD) 基于改进的部分,可变形模型 - opencv_contrib / DPM模块(蛟龙徐本斯匈牙利) 采用核心化相关滤波实时多目标跟踪 - opencv_contrib /跟踪模块(拉克索诺Kurnianggoro,费尔南多·伊格莱西亚斯J.加西亚) 改进和扩展场景文字探测 - opencv_contrib /文本模块(圣路易斯戈麦斯,瓦迪姆Pisarevsky) 立体声对应的改进 - opencv_contrib /立体声模块(米尔恰·保罗·穆雷桑,谢尔盖·诺索夫) 结构光系统标定 - opencv_contrib / structured_light(罗伯塔拉瓦内利,迪莉娅Passalacqua,斯特凡诺法布里,克劳迪娅Rapuano) 棋盘+ ArUco摄像机标定 - opencv_contrib / aruco(塞尔吉奥·加里多,人员Prasanna,加里Bradski) 通用接口深层神经网络框架的实现 - opencv_contrib / DNN模块(维塔利Lyudvichenko,阿纳托利Baksheev) 在边缘感知滤波的最新进展,提高SGBM立体算法 - OpenCV的/ calib3d和opencv_contrib / ximgproc(亚历山大Bokov,马克西姆Shabunin) 改进ICF探测器,waldboost实现 - opencv_contrib / xobjdetect(弗拉德Shakhuro,亚历山大Bovyrin) 多目标TLD跟踪 - opencv_contrib /跟踪模块(弗拉基米尔泰安,安东内拉Cascitelli) 三维姿态估计使用细胞神经网络 - opencv_contrib / cnn_3dobj(汪义达,Manuele Tamburrano,斯特凡诺法布里)
##一点背景知识 OpenCV 是一个开源的计算机视觉和机器学习库。它包含成千上万优化过的算法,为各种计算机视觉应用提供了一个通用工具包。根据这个项目的关于页面,OpenCV 已被广泛运用在各种项目上,从谷歌街景的图片拼接,到交互艺术展览的技术实现中,都有 OpenCV 的身影。 OpenCV 起始于 1999 年 Intel 的一个内部研究项目。从那时起,它的开发就一直很活跃。进化到现在,它已支持如 OpenCL 和 OpenGL 等现代技术,也支持如 iOS 和 Android 等平台。 1999 年,半条命发布后大红大热。Intel 奔腾 3 处理器是当时最高级的 CPU,400-500 MHZ 的时钟频率已被认为是相当快。2006 年 OpenCV 1.0 版本发布的时候,当时主流 CPU 的性能也只和 iPhone 5 的 A6 处理器相当。尽管计算机视觉从传统上被认为是计算密集型应用,但我们的移动设备性能已明显地超出能够执行有用的计算机视觉任务的阈值,带着摄像头的移动设备可以在计算机视觉平台上大有所为。 在本文中,我会从一个 iOS 开发者的视角概述一下 OpenCV,并介绍一点基础的类和概念。随后,会讲到如何集成 OpenCV 到你的 iOS 项目中以及一些 Objective-C++ 基础知识。最后,我们会看一个 demo 项目,看看如何在 iOS 设备上使用 OpenCV 实现人脸检测与人脸识别。 ##OpenCV 概述 ###概念 OpenCV 的 API 是 C++ 的。它由不同的模块组成,这些模块中包含范围极为广泛的各种方法,从底层的图像颜色空间转换到高层的机器学习工具。 使用 C++ API 并不是绝大多数 iOS 开发者每天都做的事,你需要使用 Objective-C++ 文件来调用 OpenCV 的函数。 也就是说,你不能在 Swift 或者 Objective-C 语言内调用 OpenCV 的函数。 这篇 OpenCV 的 iOS 教程告诉你只要把所有用到 OpenCV 的类的文件后缀名改为 .mm 就行了,包括视图控制器类也是如此。这么干或许能行得通,却不是什么好主意。正确的方式是给所有你要在 app 中使用到的 OpenCV 功能写一层 Objective-C++ 封装。这些 Objective-C++ 封装把 OpenCV 的 C++ API 转化为安全的 Objective-C API,以方便地在所有 Objective-C 类中使用。走封装的路子,你的工程中就可以只在这些封装中调用 C++ 代码,从而避免掉很多让人头痛的问题,比如直接改文件后缀名会因为在错误的文件中引用了一个 C++ 头文件而产生难以追踪的编译错误。 OpenCV 声明了命名空间 cv,因此 OpenCV 的类的前面会有个 cv:: 前缀,就像 cv::Mat、cv::Algorithm 等等。你也可以在 .mm 文件中使用 using namespace cv 来避免在一堆类名前使用 cv::前缀。但是,在某些类名前你必须使用命名空间前缀,比如 cv::Rect 和 cv::Point,因为它们会跟定义在MacTypes.h 中的 Rect 和 Point 相冲突。尽管这只是个人偏好问题,我还是偏向在任何地方都使用 cv::以保持一致性。 ###模块 下面是在官方文档中列出的最重要的模块。 core:简洁的核心模块,定义了基本的数据结构,包括稠密多维数组 Mat 和其他模块需要的基本函数。 imgproc:图像处理模块,包括线性和非线性图像滤波、几何图像转换 (缩放、仿射与透视变换、一般性基于表的重映射)、颜色空间转换、直方图等等。 video:视频分析模块,包括运动估计、背景消除、物体跟踪算法。 calib3d:包括基本的多视角几何算法、单体和立体相机的标定、对象姿态估计、双目立体匹配算法和元素的三维重建。 features2d:包含了显著特征检测算法、描述算子和算子匹配算法。 objdetect:物体检测和一些预定义的物体的检测 (如人脸、眼睛、杯子、人、汽车等)。 ml:多种机器学习算法,如 K 均值、支持向量机和神经网络。 highgui:一个简单易用的接口,提供视频捕捉、图像和视频编码等功能,还有简单的 UI 接口 (iOS 上可用的仅是其一个子集)。 gpu:OpenCV 中不同模块的 GPU 加速算法 (iOS 上不可用)。 ocl:使用 OpenCL 实现的通用算法 (iOS 上不可用)。 一些其它辅助模块,如 Python 绑定和用户贡献的算法。 基础类和操作 OpenCV 包含几百个类。为简便起见,我们只看几个基础的类和操作,进一步阅读请参考全部文档。过一遍这几个核心类应该足以对这个库的机理产生一些感觉认识。 ####cv::Mat cv::Mat 是 OpenCV 的核心数据结构,用来表示任意 N 维矩阵。因为图像只是 2 维矩阵的一个特殊场景,所以也是使用 cv::Mat 来表示的。也就是说,cv::Mat 将是你在 OpenCV 中用到最多的类。 一个 cv::Mat 实例的作用就像是图像数据的头,其中包含着描述图像格式的信息。图像数据只是被引用,并能为多个 cv::Mat 实例共享。OpenCV 使用类似于 ARC 的引用计数方法,以保证当最后一个来自 cv::Mat 的引用也消失的时候,图像数据会被释放。图像数据本身是图像连续的行的数组 (对 N 维矩阵来说,这个数据是由连续的 N-1 维数据组成的数组)。使用 step[] 数组中包含的值,图像的任一像素地址都可通过下面的指针运算得到: uchar *pixelPtr = cvMat.data + rowIndex * cvMat.step[0] + colIndex * cvMat.step[1] 每个像素的数据格式可以通过 type() 方法获得。除了常用的每通道 8 位无符号整数的灰度图 (1 通道,CV_8UC1) 和彩色图 (3 通道,CV_8UC3),OpenCV 还支持很多不常用的格式,例如 CV_16SC3 (每像素 3 通道,每通道使用 16 位有符号整数),甚至 CV_64FC4 (每像素 4 通道,每通道使用 64 位浮点数)。 ####cv::Algorithm Algorithm 是 OpenCV 中实现的很多算法的抽象基类,包括将在我们的 demo 工程中用到的FaceRecognizer。它提供的 API 与苹果的 Core Image 框架中的 CIFilter 有些相似之处。创建一个Algorithm 的时候使用算法的名字来调用 Algorithm::create(),并且可以通过 get() 和 set()方法来获取和设置各个参数,这有点像是键值编码。另外,Algorithm 从底层就支持从/向 XML 或 YAML 文件加载/保存参数的功能。 在 iOS 上使用 OpenCV ###添加 OpenCV 到你的工程中 集成 OpenCV 到你的工程中有三种方法: 使用 CocoaPods 就好: pod "OpenCV"。 下载官方 iOS 框架发行包,并把它添加到工程里。 从 GitHub 拉下代码,并根据教程自己编译 OpenCV 库。 ###Objective-C++ 如前面所说,OpenCV 是一个 C++ 的 API,因此不能直接在 Swift 和 Objective-C 代码中使用,但能在 Objective-C++ 文件中使用。 Objective-C++ 是 Objective-C 和 C++ 的混合物,让你可以在 Objective-C 类中使用 C++ 对象。clang 编译器会把所有后缀名为 .mm 的文件都当做是 Objective-C++。一般来说,它会如你所期望的那样运行,但还是有一些使用 Objective-C++ 的注意事项。内存管理是你最应该格外注意的点,因为 ARC 只对 Objective-C 对象有效。当你使用一个 C++ 对象作为类属性的时候,其唯一有效的属性就是 assign。因此,你的 dealloc 函数应确保 C++ 对象被正确地释放了。 第二重要的点就是,如果你在 Objective-C++ 头文件中引入了 C++ 头文件,当你在工程中使用该 Objective-C++ 文件的时候就泄露了 C++ 的依赖。任何引入你的 Objective-C++ 类的 Objective-C 类也会引入该 C++ 类,因此该 Objective-C 文件也要被声明为 Objective-C++ 的文件。这会像森林大火一样在工程中迅速蔓延。所以,应该把你引入 C++ 文件的地方都用 #ifdef __cplusplus 包起来,并且只要可能,就尽量只在 .mm实现文件中引入 C++ 头文件。 要获得更多如何混用 C++ 和 Objective-C 的细节,请查看 Matt Galloway 写的这篇教程。 Demo:人脸检测与识别 现在,我们对 OpenCV 及如何把它集成到我们的应用中有了大概认识,那让我们来做一个小 demo 应用:从 iPhone 的摄像头获取视频流,对它持续进行人脸检测,并在屏幕上标出来。当用户点击一个脸孔时,应用会尝试识别这个人。如果识别结果正确,用户必须点击 “Correct”。如果识别错误,用户必须选择正确的人名来纠正错误。我们的人脸识别器就会从错误中学习,变得越来越好。 本 demo 应用的源码可从 GitHub 获得。 ###视频拍摄 OpenCV 的 highgui 模块中有个类,CvVideoCamera,它把 iPhone 的摄像机抽象出来,让我们的 app 通过一个代理函数 - (void)processImage:(cv::Mat&)image 来获得视频流。CvVideoCamera 实例可像下面这样进行设置: CvVideoCamera *videoCamera = [[CvVideoCamera alloc] initWithParentView:view]; videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront; videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset640x480; videoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait; videoCamera.defaultFPS = 30; videoCamera.grayscaleMode = NO; videoCamera.delegate = self; 摄像头的帧率被设置为 30 帧每秒, 我们实现的 processImage 函数将每秒被调用 30 次。因为我们的 app 要持续不断地检测人脸,所以我们应该在这个函数里实现人脸的检测。要注意的是,如果对某一帧进行人脸检测的时间超过 1/30 秒,就会产生掉帧现象。 ###人脸检测 其实你并不需要使用 OpenCV 来做人脸检测,因为 Core Image 已经提供了 CIDetector 类。用它来做人脸检测已经相当好了,并且它已经被优化过,使用起来也很容易: CIDetector *faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}]; NSArray *faces = [faceDetector featuresInImage:image]; 从该图片中检测到的每一张面孔都在数组 faces 中保存着一个 CIFaceFeature 实例。这个实例中保存着这张面孔的所处的位置和宽高,除此之外,眼睛和嘴的位置也是可选的。 另一方面,OpenCV 也提供了一套物体检测功能,经过训练后能够检测出任何你需要的物体。该库为多个场景自带了可以直接拿来用的检测参数,如人脸、眼睛、嘴、身体、上半身、下半身和笑脸。检测引擎由一些非常简单的检测器的级联组成。这些检测器被称为 Haar 特征检测器,它们各自具有不同的尺度和权重。在训练阶段,决策树会通过已知的正确和错误的图片进行优化。关于训练与检测过程的详情可参考此原始论文。当正确的特征级联及其尺度与权重通过训练确立以后,这些参数就可被加载并初始化级联分类器了: // 正面人脸检测器训练参数的文件路径 NSString *faceCascadePath = [[NSBundle mainBundle] pathForResource:@"haarcascade_frontalface_alt2" ofType:@"xml"]; const CFIndex CASCADE_NAME_LEN = 2048; char *CASCADE_NAME = (char *) malloc(CASCADE_NAME_LEN); CFStringGetFileSystemRepresentation( (CFStringRef)faceCascadePath, CASCADE_NAME, CASCADE_NAME_LEN); CascadeClassifier faceDetector; faceDetector.load(CASCADE_NAME); 这些参数文件可在 OpenCV 发行包里的 data/haarcascades 文件夹中找到。 在使用所需要的参数对人脸检测器进行初始化后,就可以用它进行人脸检测了: cv::Mat img; vector<cv::Rect> faceRects; double scalingFactor = 1.1; int minNeighbors = 2; int flags = 0; cv::Size minimumSize(30,30); faceDetector.detectMultiScale(img, faceRects, scalingFactor, minNeighbors, flags cv::Size(30, 30) ); 检测过程中,已训练好的分类器会用不同的尺度遍历输入图像的每一个像素,以检测不同大小的人脸。参数scalingFactor 决定每次遍历分类器后尺度会变大多少倍。参数 minNeighbors 指定一个符合条件的人脸区域应该有多少个符合条件的邻居像素才被认为是一个可能的人脸区域;如果一个符合条件的人脸区域只移动了一个像素就不再触发分类器,那么这个区域非常可能并不是我们想要的结果。拥有少于 minNeighbors 个符合条件的邻居像素的人脸区域会被拒绝掉。如果 minNeighbors 被设置为 0,所有可能的人脸区域都会被返回回来。参数 flags 是 OpenCV 1.x 版本 API 的遗留物,应该始终把它设置为 0。最后,参数 minimumSize 指定我们所寻找的人脸区域大小的最小值。faceRects 向量中将会包含对 img 进行人脸识别获得的所有人脸区域。识别的人脸图像可以通过 cv::Mat 的 () 运算符提取出来,调用方式很简单:cv::Mat faceImg = img(aFaceRect)。 不管是使用 CIDetector 还是 OpenCV 的 CascadeClassifier,只要我们获得了至少一个人脸区域,我们就可以对图像中的人进行识别了。 ###人脸识别 OpenCV 自带了三个人脸识别算法:Eigenfaces,Fisherfaces 和局部二值模式直方图 (LBPH)。如果你想知道它们的工作原理及相互之间的区别,请阅读 OpenCV 的详细文档。 针对于我们的 demo app,我们将采用 LBPH 算法。因为它会根据用户的输入自动更新,而不需要在每添加一个人或纠正一次出错的判断的时候都要重新进行一次彻底的训练。 要使用 LBPH 识别器,我们也用 Objective-C++ 把它封装起来。这个封装中暴露以下函数: + (FJFaceRecognizer *)faceRecognizerWithFile:(NSString *)path; - (NSString *)predict:(UIImage*)img confidence:(double *)confidence; - (void)updateWithFace:(UIImage *)img name:(NSString *)name; 像下面这样用工厂方法来创建一个 LBPH 实例: + (FJFaceRecognizer *)faceRecognizerWithFile:(NSString *)path { FJFaceRecognizer *fr = [FJFaceRecognizer new]; fr->_faceClassifier = createLBPHFaceRecognizer(); fr->_faceClassifier->load(path.UTF8String); return fr; } 预测函数可以像下面这样实现: - (NSString *)predict:(UIImage*)img confidence:(double *)confidence { cv::Mat src = [img cvMatRepresentationGray]; int label; self->_faceClassifier->predict(src, label, *confidence); return _labelsArray[label]; } 请注意,我们要使用一个类别方法把 UIImage 转化为 cv::Mat。此转换本身倒是相当简单直接:使用CGBitmapContextCreate 创建一个指向 cv::Image 中的 data 指针所指向的数据的 CGContextRef。当我们在此图形上下文中绘制此 UIImage 的时候,cv::Image 的 data 指针所指就是所需要的数据。更有趣的是,我们能对一个 Objective-C 类创建一个 Objective-C++ 的类别,并且确实管用。 另外,OpenCV 的人脸识别器仅支持整数标签,但是我们想使用人的名字作标签,所以我们得通过一个NSArray 属性来对二者实现简单的转换。 一旦识别器给了我们一个识别出来的标签,我们把此标签给用户看,这时候就需要用户给识别器一个反馈。用户可以选择,“是的,识别正确”,也可以选择,“不,这是 Y,不是 X”。在这两种情况下,我们都可以通过人脸图像和正确的标签来更新 LBPH 模型,以提高未来识别的性能。使用用户的反馈来更新人脸识别器的方式如下: - (void)updateWithFace:(UIImage *)img name:(NSString *)name { cv::Mat src = [img cvMatRepresentationGray]; NSInteger label = [_labelsArray indexOfObject:name]; if (label == NSNotFound) { [_labelsArray addObject:name]; label = [_labelsArray indexOfObject:name]; } vector<cv::Mat> images = vector<cv::Mat>(); images.push_back(src); vector<int> labels = vector<int>(); labels.push_back((int)label); self->_faceClassifier->update(images, labels); } 这里,我们又做了一次了从 UIImage 到 cv::Mat、int 到 NSString 标签的转换。我们还得如 OpenCV 的FaceRecognizer::update API所期望的那样,把我们的参数放到 std::vector 实例中去。 如此“预测,获得反馈,更新循环”,就是文献上所说的监督式学习。 ##结论 OpenCV 是一个强大而用途广泛的库,覆盖了很多现如今仍在活跃的研究领域。想在一篇文章中给出详细的使用说明只会是让人徒劳的事情。因此,本文仅意在从较高层次对 OpenCV 库做一个概述。同时,还试图就如何集成 OpenCV 库到你的 iOS 工程中给出一些实用建议,并通过一个人脸识别的例子来向你展示如何在一个真正的项目中使用 OpenCV。如果你觉得 OpenCV 对你的项目有用, OpenCV 的官方文档写得非常好非常详细,请继续前行,创造出下一个伟大的 app! 原文 Face Recognition with OpenCV
OpenCV中的级联分类器Cascade Classifier Goal In this tutorial you will learn how to: Use the CascadeClassifier class to detect objects in a video stream. Particularly, we will use the functions: load to load a .xml classifier file. It can be either a Haar or a LBP classifer detectMultiScale to perform the detection. Code This tutorial code’s is shown lines below. You can also download it from here . The second version (using LBP for face detection) can be found here #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; /** Function Headers */ void detectAndDisplay( Mat frame ); /** Global variables */ String face_cascade_name = "haarcascade_frontalface_alt.xml"; String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml"; CascadeClassifier face_cascade; CascadeClassifier eyes_cascade; string window_name = "Capture - Face detection"; RNG rng(12345); /** @function main */ int main( int argc, const char** argv ) { CvCapture* capture; Mat frame; //-- 1. Load the cascades if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; }; if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; }; //-- 2. Read the video stream capture = cvCaptureFromCAM( -1 ); if( capture ) { while( true ) { frame = cvQueryFrame( capture ); //-- 3. Apply the classifier to the frame if( !frame.empty() ) { detectAndDisplay( frame ); } else { printf(" --(!) No captured frame -- Break!"); break; } int c = waitKey(10); if( (char)c == 'c' ) { break; } } } return 0; } /** @function detectAndDisplay */ void detectAndDisplay( Mat frame ) { std::vector<Rect> faces; Mat frame_gray; cvtColor( frame, frame_gray, CV_BGR2GRAY ); equalizeHist( frame_gray, frame_gray ); //-- Detect faces face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) ); for( size_t i = 0; i < faces.size(); i++ ) { Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 ); ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 ); Mat faceROI = frame_gray( faces[i] ); std::vector<Rect> eyes; //-- In each face, detect eyes eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) ); for( size_t j = 0; j < eyes.size(); j++ ) { Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5, faces[i].y + eyes[j].y + eyes[j].height*0.5 ); int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 ); circle( frame, center, radius, Scalar( 255, 0, 0 ), 4, 8, 0 ); } } //-- Show what you got imshow( window_name, frame ); } 第 1 段(可获 2 积分) 翻译此段 Result Here is the result of running the code above and using as input the video stream of a build-in webcam: Remember to copy the files haarcascade_frontalface_alt.xml and haarcascade_eye_tree_eyeglasses.xml in your current directory. They are located in opencv/data/haarcascades This is the result of using the file lbpcascade_frontalface.xml (LBP trained) for the face detection. For the eyes we keep using the file used in the tutorial.
使用 gcc 和 CMake 编译简单的 OpenCV 程序 注意 我们假设你已经成功的安装 OpenCV 。 在你的项目中使用 OpenCV 的最简单方式是用 CMake. 优点是 (来自官方 Wiki): 支持 Windows 和 Linux,无需任何改动 和轻松和其他支持 CMake 的工具一起使用( 例如 Qt, ITK 和 VTK ) 如果你对 CMake 不熟悉,请参考 教程 步骤 使用 OpenCV 创建一个简单应用 DisplayImage.cpp 如下 #include <stdio.h> #include <opencv2/opencv.hpp> using namespace cv; int main(int argc, char** argv ) { if ( argc != 2 ) { printf("usage: DisplayImage.out <Image_Path>\n"); return -1; } Mat image; image = imread( argv[1], 1 ); if ( !image.data ) { printf("No image data \n"); return -1; } namedWindow("Display Image", WINDOW_AUTOSIZE ); imshow("Display Image", image); waitKey(0); return 0; } 创建 CMake 文件 现在你需要创建一个 CMakeLists.txt 文件,内容如下: cmake_minimum_required(VERSION 2.8) project( DisplayImage ) find_package( OpenCV REQUIRED ) add_executable( DisplayImage DisplayImage.cpp ) target_link_libraries( DisplayImage ${OpenCV_LIBS} ) 生成可执行文件 这部分很简单,使用如下命令构建即可: cd <DisplayImage_directory> cmake . make 结果 现在你已经有一个可执行程序(名为 DisplayImage ). 你只需要传递一个图片文件即可运行: ./DisplayImage lena.jpg 运行结果:
研究人的视网膜并用于图像处理 [OpenCV] 目标 这篇文章主要呈现了一个人类视网膜模型,用于展示一些有趣图像处理和增强的特性。在这篇文章中你将学到: 从你的视网膜中发掘两个主通道 视网膜模型的基本使用 视网膜处理的一些参数调整 总体概述 该模型源于 Jeanny Herault 在 Gipsa 的研究,这是一个关于使用 Listic (code maintainer) 进行图像处理的实验室。这并非完整的模型,但是已经可以呈现一些有趣的事情,这些可以用于增强图像的处理体验。该模型可以使用人类的视网膜信息: spectral whitening that has 3 important effects: high spatio-temporal frequency signals canceling (noise), mid-frequencies details enhancement and low frequencies luminance energy reduction. This all in one property directly allows visual signals cleaning of classical undesired distortions introduced by image sensors and input luminance range. local logarithmic luminance compression allows details to be enhanced even in low light conditions. decorrelation of the details information (Parvocellular output channel) and transient information (events, motion made available at the Magnocellular output channel). The first two points are illustrated below : In the figure below, the OpenEXR image sample CrissyField.exr, a High Dynamic Range image is shown. In order to make it visible on this web-page, the original input image is linearly rescaled to the classical image luminance range [0-255] and is converted to 8bit/channel format. Such strong conversion hides many details because of too strong local contrasts. Furthermore, noise energy is also strong and pollutes visual information. In the following image, as your retina does, local luminance adaptation, spatial noise removal and spectral whitening work together and transmit accurate information on lower range 8bit data channels. On this picture, noise in significantly removed, local details hidden by strong luminance contrasts are enhanced. Output image keeps its naturalness and visual content is enhanced. Note : image sample can be downloaded from the OpenEXR website. Regarding this demonstration, before retina processing, input image has been linearly rescaled within 0-255 keeping its channels float format. 5% of its histogram ends has been cut (mostly removes wrong HDR pixels). Check out the sample opencv/samples/cpp/OpenEXRimages_HDR_Retina_toneMapping.cpp for similar processing. The following demonstration will only consider classical 8bit/channel images. The retina model output channels The retina model presents two outputs that benefit from the above cited behaviors. The first one is called the Parvocellular channel. It is mainly active in the foveal retina area (high resolution central vision with color sensitive photo-receptors), its aim is to provide accurate color vision for visual details remaining static on the retina. On the other hand objects moving on the retina projection are blurred. The second well known channel is the Magnocellular channel. It is mainly active in the retina peripheral vision and send signals related to change events (motion, transient events, etc.). These outing signals also help visual system to focus/center retina on ‘transient’/moving areas for more detailed analysis thus improving visual scene context and object classification. NOTE : regarding the proposed model, contrary to the real retina, we apply these two channels on the entire input images using the same resolution. This allows enhanced visual details and motion information to be extracted on all the considered images... but remember, that these two channels are complementary. For example, if Magnocellular channel gives strong energy in an area, then, the Parvocellular channel is certainly blurred there since there is a transient event. As an illustration, we apply in the following the retina model on a webcam video stream of a dark visual scene. In this visual scene, captured in an amphitheater of the university, some students are moving while talking to the teacher. In this video sequence, because of the dark ambiance, signal to noise ratio is low and color artifacts are present on visual features edges because of the low quality image capture tool-chain. Below is shown the retina foveal vision applied on the entire image. In the used retina configuration, global luminance is preserved and local contrasts are enhanced. Also, signal to noise ratio is improved : since high frequency spatio-temporal noise is reduced, enhanced details are not corrupted by any enhanced noise. Below is the output of the Magnocellular output of the retina model. Its signals are strong where transient events occur. Here, a student is moving at the bottom of the image thus generating high energy. The remaining of the image is static however, it is corrupted by a strong noise. Here, the retina filters out most of the noise thus generating low false motion area ‘alarms’. This channel can be used as a transient/moving areas detector : it would provide relevant information for a low cost segmentation tool that would highlight areas in which an event is occurring. Retina use case This model can be used basically for spatio-temporal video effects but also in the aim of : performing texture analysis with enhanced signal to noise ratio and enhanced details robust against input images luminance ranges (check out the Parvocellular retina channel output) performing motion analysis also taking benefit of the previously cited properties. For more information, refer to the following papers : Benoit A., Caplier A., Durette B., Herault, J., “Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing”, Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI <http://dx.doi.org/10.1016/j.cviu.2010.01.011> Please have a look at the reference work of Jeanny Herault that you can read in his book : Vision: Images, Signals and Neural Networks: Models of Neural Processing in Visual Perception (Progress in Neural Processing),By: Jeanny Herault, ISBN: 9814273686. WAPI (Tower ID): 113266891. This retina filter code includes the research contributions of phd/research collegues from which code has been redrawn by the author : take a look at the retinacolor.hpp module to discover Brice Chaix de Lavarene phD color mosaicing/demosaicing and his reference paper: B. Chaix de Lavarene, D. Alleysson, B. Durette, J. Herault (2007). “Efficient demosaicing through recursive filtering”, IEEE International Conference on Image Processing ICIP 2007 take a look at imagelogpolprojection.hpp to discover retina spatial log sampling which originates from Barthelemy Durette phd with Jeanny Herault. A Retina / V1 cortex projection is also proposed and originates from Jeanny’s discussions. ====> more information in the above cited Jeanny Heraults’s book. Code tutorial Please refer to the original tutorial source code in file opencv_folder/samples/cpp/tutorial_code/contrib/retina_tutorial.cpp. To compile it, assuming OpenCV is correctly installed, use the following command. It requires the opencv_core (cv::Mat and friends objects management), opencv_highgui (display and image/video read) and opencv_contrib (Retina description) libraries to compile. // compile gcc retina_tutorial.cpp -o Retina_tuto -lopencv_core -lopencv_highgui -lopencv_contrib // Run commands : add 'log' as a last parameter to apply a spatial log sampling (simulates retina sampling) // run on webcam ./Retina_tuto -video // run on video file ./Retina_tuto -video myVideo.avi // run on an image ./Retina_tuto -image myPicture.jpg // run on an image with log sampling ./Retina_tuto -image myPicture.jpg log 下面是代码的解释: 视网膜定义出现在 contrib 包中,并包含一个简单的使用示例: #include "opencv2/opencv.hpp" 通过一个 help 函数为用户提供一些运行的提示: // the help procedure static void help(std::string errorMessage) { std::cout<<"Program init error : "<<errorMessage<<std::endl; std::cout<<"\nProgram call procedure : retinaDemo [processing mode] [Optional : media target] [Optional LAST parameter: \"log\" to activate retina log sampling]"<<std::endl; std::cout<<"\t[processing mode] :"<<std::endl; std::cout<<"\t -image : for still image processing"<<std::endl; std::cout<<"\t -video : for video stream processing"<<std::endl; std::cout<<"\t[Optional : media target] :"<<std::endl; std::cout<<"\t if processing an image or video file, then, specify the path and filename of the target to process"<<std::endl; std::cout<<"\t leave empty if processing video stream coming from a connected video device"<<std::endl; std::cout<<"\t[Optional : activate retina log sampling] : an optional last parameter can be specified for retina spatial log sampling"<<std::endl; std::cout<<"\t set \"log\" without quotes to activate this sampling, output frame size will be divided by 4"<<std::endl; std::cout<<"\nExamples:"<<std::endl; std::cout<<"\t-Image processing : ./retinaDemo -image lena.jpg"<<std::endl; std::cout<<"\t-Image processing with log sampling : ./retinaDemo -image lena.jpg log"<<std::endl; std::cout<<"\t-Video processing : ./retinaDemo -video myMovie.mp4"<<std::endl; std::cout<<"\t-Live video processing : ./retinaDemo -video"<<std::endl; std::cout<<"\nPlease start again with new parameters"<<std::endl; std::cout<<"****************************************************"<<std::endl; std::cout<<" NOTE : this program generates the default retina parameters file 'RetinaDefaultParameters.xml'"<<std::endl; std::cout<<" => you can use this to fine tune parameters and load them if you save to file 'RetinaSpecificParameters.xml'"<<std::endl; } Then, start the main program and first declare a cv::Mat matrix in which input images will be loaded. Also allocate a cv::VideoCapture object ready to load video streams (if necessary) int main(int argc, char* argv[]) { // declare the retina input buffer... that will be fed differently in regard of the input media cv::Mat inputFrame; cv::VideoCapture videoCapture; // in case a video media is used, its manager is declared here In the main program, before processing, first check input command parameters. Here it loads a first input image coming from a single loaded image (if user chose command -image) or from a video stream (if user chose command -video). Also, if the user added log command at the end of its program call, the spatial logarithmic image sampling performed by the retina is taken into account by the Boolean flag useLogSampling. // welcome message std::cout<<"****************************************************"<<std::endl; std::cout<<"* Retina demonstration : demonstrates the use of is a wrapper class of the Gipsa/Listic Labs retina model."<<std::endl; std::cout<<"* This demo will try to load the file 'RetinaSpecificParameters.xml' (if exists).\nTo create it, copy the autogenerated template 'RetinaDefaultParameters.xml'.\nThen twaek it with your own retina parameters."<<std::endl; // basic input arguments checking if (argc<2) { help("bad number of parameter"); return -1; } bool useLogSampling = !strcmp(argv[argc-1], "log"); // check if user wants retina log sampling processing std::string inputMediaType=argv[1]; ////////////////////////////////////////////////////////////////////////////// // 检查输入的媒体类型 (静态图片、视频文件以及现场视频采集) if (!strcmp(inputMediaType.c_str(), "-image") && argc >= 3) { std::cout<<"RetinaDemo: processing image "<<argv[2]<<std::endl; // image processing case inputFrame = cv::imread(std::string(argv[2]), 1); // load image in BGR color mode }else if (!strcmp(inputMediaType.c_str(), "-video")) { if (argc == 2 || (argc == 3 && useLogSampling)) // attempt to grab images from a video capture device { videoCapture.open(0); }else// attempt to grab images from a video filestream { std::cout<<"RetinaDemo: processing video stream "<<argv[2]<<std::endl; videoCapture.open(argv[2]); } // grab a first frame to check if everything is ok videoCapture>>inputFrame; }else { // bad command parameter help("bad command parameter"); return -1; } Once all input parameters are processed, a first image should have been loaded, if not, display error and stop program : if (inputFrame.empty()) { help("Input media could not be loaded, aborting"); return -1; } Now, everything is ready to run the retina model. I propose here to allocate a retina instance and to manage the eventual log sampling option. The Retina constructor expects at least a cv::Size object that shows the input data size that will have to be managed. One can activate other options such as color and its related color multiplexing strategy (here Bayer multiplexing is chosen using enum cv::RETINA_COLOR_BAYER). If using log sampling, the image reduction factor (smaller output images) and log sampling strengh can be adjusted. // pointer to a retina object cv::Ptr<cv::Retina> myRetina; // if the last parameter is 'log', then activate log sampling (favour foveal vision and subsamples peripheral vision) if (useLogSampling) { myRetina = new cv::Retina(inputFrame.size(), true, cv::RETINA_COLOR_BAYER, true, 2.0, 10.0); } else// -> else allocate "classical" retina : myRetina = new cv::Retina(inputFrame.size()); 一旦完成,上述代码就会生成一个默认 xml 文件,包含视网膜的默认参数。这个可以让你很方便的在你的配置中使用这个模板。这里所生成的模板 xml 文件名为 RetinaDefaultParameters.xml. // 保存默认视网膜参数文件,以便阅读并修改然后重载 myRetina->write("RetinaDefaultParameters.xml"); 紧接着,程序视图加载另外一个名为 RetinaSpecificParameters.xml 的 XML 文件文件。如果你已经创建了这个文件并引入,那么该文件会被加载,否则将使用默认的视网膜参数。 // 如果文件存在则加载 myRetina->setup("RetinaSpecificParameters.xml"); 这个文件并不是必须的,只是提供了这样一个选择,你可以清空视网膜缓冲区来强制清除以往事件。 // 重置所有视网膜缓冲区 (想想你闭上眼睛很长时间) myRetina->clearBuffers(); 现在可以运行视网膜程序了,首先我创建了一个输出缓冲区用于接收两个视网膜频道的输出: // declare retina output buffers cv::Mat retinaOutput_parvo; cv::Mat retinaOutput_magno; 然后在循环中运行视网膜,根据需要从视频序列中加载新的帧,并将输出写回专用缓冲区。 // 无限循环 while(true) { // if using video stream, then, grabbing a new frame, else, input remains the same if (videoCapture.isOpened()) videoCapture>>inputFrame; // run retina filter on the loaded input frame myRetina->run(inputFrame); // Retrieve and display retina output myRetina->getParvo(retinaOutput_parvo); myRetina->getMagno(retinaOutput_magno); cv::imshow("retina input", inputFrame); cv::imshow("Retina Parvo", retinaOutput_parvo); cv::imshow("Retina Magno", retinaOutput_magno); cv::waitKey(10); } 这样就完成了。但是如果你想确保系统安全,请小心管理异常。该程序在一些无效数据会发生异常,例如没有输入帧、错误的设置等等。接下来我建议使用 try catch 代码块来执行视网膜处理代码,如下所示: try{ // pointer to a retina object cv::Ptr<cv::Retina> myRetina; [---] // processing loop with no stop condition while(true) { [---] } }catch(cv::Exception e) { std::cerr<<"Error using Retina : "<<e.what()<<std::endl; } 视网膜参数该如何处理? 首先,推荐如下参考论文: Benoit A., Caplier A., Durette B., Herault, J., “Using Human Visual System Modeling For Bio-Inspired Low Level Image Processing”, Elsevier, Computer Vision and Image Understanding 114 (2010), pp. 758-773. DOI <http://dx.doi.org/10.1016/j.cviu.2010.01.011> 然后打开上述示例代码生成的配置文件 RetinaDefaultParameters.xml ,内容如下: <?xml version="1.0"?> <opencv_storage> <OPLandIPLparvo> <colorMode>1</colorMode> <normaliseOutput>1</normaliseOutput> <photoreceptorsLocalAdaptationSensitivity>7.0e-01</photoreceptorsLocalAdaptationSensitivity> <photoreceptorsTemporalConstant>5.0e-01</photoreceptorsTemporalConstant> <photoreceptorsSpatialConstant>5.3e-01</photoreceptorsSpatialConstant> <horizontalCellsGain>0.</horizontalCellsGain> <hcellsTemporalConstant>1.</hcellsTemporalConstant> <hcellsSpatialConstant>7.</hcellsSpatialConstant> <ganglionCellsSensitivity>7.0e-01</ganglionCellsSensitivity></OPLandIPLparvo> <IPLmagno> <normaliseOutput>1</normaliseOutput> <parasolCells_beta>0.</parasolCells_beta> <parasolCells_tau>0.</parasolCells_tau> <parasolCells_k>7.</parasolCells_k> <amacrinCellsTemporalCutFrequency>1.2e+00</amacrinCellsTemporalCutFrequency> <V0CompressionParameter>9.5e-01</V0CompressionParameter> <localAdaptintegration_tau>0.</localAdaptintegration_tau> <localAdaptintegration_k>7.</localAdaptintegration_k></IPLmagno> </opencv_storage> Here are some hints but actually, the best parameter setup depends more on what you want to do with the retina rather than the images input that you give to retina. Apart from the more specific case of High Dynamic Range images (HDR) that require more specific setup for specific luminance compression objective, the retina behaviors should be rather stable from content to content. Note that OpenCV is able to manage such HDR format thanks to the OpenEXR images compatibility. Then, if the application target requires details enhancement prior to specific image processing, you need to know if mean luminance information is required or not. If not, the the retina can cancel or significantly reduce its energy thus giving more visibility to higher spatial frequency details. Basic parameters The most simple parameters are the following : colorMode : let the retina process color information (if 1) or gray scale images (if 0). In this last case, only the first channel of the input will be processed. normaliseOutput : each channel has this parameter, if value is 1, then the considered channel output is rescaled between 0 and 255. Take care in this case at the Magnocellular output level (motion/transient channel detection). Residual noise will also be rescaled ! Note : using color requires color channels multiplexing/demultipexing which requires more processing. You can expect much faster processing using gray levels : it would require around 30 product per pixel for all the retina processes and it has recently been parallelized for multicore architectures. Photo-receptors parameters The following parameters act on the entry point of the retina - photo-receptors - and impact all the following processes. These sensors are low pass spatio-temporal filters that smooth temporal and spatial data and also adjust there sensitivity to local luminance thus improving details extraction and high frequency noise canceling. photoreceptorsLocalAdaptationSensitivity between 0 and 1. Values close to 1 allow high luminance log compression effect at the photo-receptors level. Values closer to 0 give a more linear sensitivity. Increased alone, it can burn the Parvo (details channel) output image. If adjusted in collaboration with ganglionCellsSensitivity images can be very contrasted whatever the local luminance there is... at the price of a naturalness decrease. photoreceptorsTemporalConstant this setups the temporal constant of the low pass filter effect at the entry of the retina. High value lead to strong temporal smoothing effect : moving objects are blurred and can disappear while static object are favored. But when starting the retina processing, stable state is reached lately. photoreceptorsSpatialConstant specifies the spatial constant related to photo-receptors low pass filter effect. This parameters specify the minimum allowed spatial signal period allowed in the following. Typically, this filter should cut high frequency noise. Then a 0 value doesn’t cut anything noise while higher values start to cut high spatial frequencies and more and more lower frequencies... Then, do not go to high if you wanna see some details of the input images ! A good compromise for color images is 0.53 since this won’t affect too much the color spectrum. Higher values would lead to gray and blurred output images. Horizontal cells parameters This parameter set tunes the neural network connected to the photo-receptors, the horizontal cells. It modulates photo-receptors sensitivity and completes the processing for final spectral whitening (part of the spatial band pass effect thus favoring visual details enhancement). horizontalCellsGain here is a critical parameter ! If you are not interested by the mean luminance and focus on details enhancement, then, set to zero. But if you want to keep some environment luminance data, let some low spatial frequencies pass into the system and set a higher value (<1). hcellsTemporalConstant similar to photo-receptors, this acts on the temporal constant of a low pass temporal filter that smooths input data. Here, a high value generates a high retina after effect while a lower value makes the retina more reactive. hcellsSpatialConstant is the spatial constant of the low pass filter of these cells filter. It specifies the lowest spatial frequency allowed in the following. Visually, a high value leads to very low spatial frequencies processing and leads to salient halo effects. Lower values reduce this effect but the limit is : do not go lower than the value ofphotoreceptorsSpatialConstant. Those 2 parameters actually specify the spatial band-pass of the retina. NOTE after the processing managed by the previous parameters, input data is cleaned from noise and luminance in already partly enhanced. The following parameters act on the last processing stages of the two outing retina signals. Parvo (details channel) dedicated parameter ganglionCellsSensitivity specifies the strength of the final local adaptation occurring at the output of this details dedicated channel. Parameter values remain between 0 and 1. Low value tend to give a linear response while higher values enforces the remaining low contrasted areas. Note : this parameter can correct eventual burned images by favoring low energetic details of the visual scene, even in bright areas. IPL Magno (motion/transient channel) parameters Once image information is cleaned, this channel acts as a high pass temporal filter that only selects signals related to transient signals (events, motion, etc.). A low pass spatial filter smooths extracted transient data and a final logarithmic compression enhances low transient events thus enhancing event sensitivity. parasolCells_beta generally set to zero, can be considered as an amplifier gain at the entry point of this processing stage. Generally set to 0. parasolCells_tau the temporal smoothing effect that can be added parasolCells_k the spatial constant of the spatial filtering effect, set it at a high value to favor low spatial frequency signals that are lower subject to residual noise. amacrinCellsTemporalCutFrequency specifies the temporal constant of the high pass filter. High values let slow transient events to be selected. V0CompressionParameter specifies the strength of the log compression. Similar behaviors to previous description but here it enforces sensitivity of transient events. localAdaptintegration_tau generally set to 0, no real use here actually localAdaptintegration_k specifies the size of the area on which local adaptation is performed. Low values lead to short range local adaptation (higher sensitivity to noise), high values secure log compression.
OpenCV 实现特征检测 目标 在这篇文章中你将学习到: 使用 FeatureDetector 接口来查找兴趣点,具体包括: 使用 SurfFeatureDetector 及其函数 detect 来执行检测过程 使用函数 drawKeypoints 来绘制检测到的关键点 代码 完整代码可从这里 下载 #include <stdio.h> #include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/nonfree/features2d.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/nonfree/nonfree.hpp" using namespace cv; void readme(); /** @function main */ int main( int argc, char** argv ) { if( argc != 3 ) { readme(); return -1; } Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ); if( !img_1.data || !img_2.data ) { std::cout<< " --(!) Error reading images " << std::endl; return -1; } //-- Step 1: Detect the keypoints using SURF Detector int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_1, keypoints_2; detector.detect( img_1, keypoints_1 ); detector.detect( img_2, keypoints_2 ); //-- Draw keypoints Mat img_keypoints_1; Mat img_keypoints_2; drawKeypoints( img_1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT ); drawKeypoints( img_2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT ); //-- Show detected (drawn) keypoints imshow("Keypoints 1", img_keypoints_1 ); imshow("Keypoints 2", img_keypoints_2 ); waitKey(0); return 0; } /** @function readme */ void readme() { std::cout << " Usage: ./SURF_detector <img1> <img2>" << std::endl; } Result
document-scanner —— 一个基于 OpenCV 的文档扫描程序 document-scanner 则是一个基于 OpenCV + Python 开发的文档扫描程序,强烈建议阅读我的博客以便更好理解本文: http://vipulsharma20.blogspot.on 真诚的感谢下面这篇文字以及作者: http://www.pyimagesearch.com/2014/09/01/build-kick-ass-mobile-document-scanner-just-5-minutes/ 这里有非常好的 OpenCV 文章以及相关信息。
OpenCV 对图片的基本操作 输入/输出 图像 从文件加载图像 Mat img = imread(filename) 如果你加载的是 JPG 文件,将会默认创建一个 3 通道的图像,如果你需要灰度图,可以用: Mat img = imread(filename, 0); 注意 文件的根据市根据文件内容自动识别的(一般是前几个字节的内容) 将突破保存到文件: imwrite(filename, img); 注意 文件的格式是通过其扩展名进行识别. 注意 请使用 imdecode 和 imencode 来读写内存中的图片,而不是文件中的图片。 XML/YAML 待处理 基本的图像操作 访问像素强度值 为了获得像素强度值,你需要知道图片的类型已经包含多少个通道。这里是一个单通道灰度图的例子(8UC1 类型), 其像素坐标为 x 和 y: Scalar intensity = img.at<uchar>(y, x); intensity.val[0] 包含了值范围从 0 到 255。请注意 x 和 y 参数的顺序。因为 OpenCV 的图像是使用和阵列相同结构的方式存储,我们使用相同的约定来处理两种方式:基于 0 的行索引(或者称为纵坐标)作为首个参数以及基于 0 的列索引(横坐标)跟进其后。你也可以使用下面的方式来代替: Scalar intensity = img.at<uchar>(Point(x, y)); 现在让我们考虑一个使用 BGR 色彩顺序 3 通道的图像(通过 imread 返回默认的格式): Vec3b intensity = img.at<Vec3b>(y, x); uchar blue = intensity.val[0]; uchar green = intensity.val[1]; uchar red = intensity.val[2]; 你可以对浮点图像使用相同的方法(例如通过运行 Sobel 在一个三通道图像来获取图像): Vec3f intensity = img.at<Vec3f>(y, x); float blue = intensity.val[0]; float green = intensity.val[1]; float red = intensity.val[2]; 可以使用相同的方法来修改像素强度: img.at<uchar>(y, x) = 128; OpenCV 的 calib3d 模块有很多函数,例如 projectPoints 使用 Mat 形式来获取 2D 和 3D 点的数组。矩阵包含了一列数据,每一行对应一个点,矩阵类型可以是 32FC2 或者 32FC3。可以使用 std::vector 来构建一个矩阵: vector<Point2f> points; //... fill the array Mat pointsMat = Mat(points); 你可以使用 Mat::at 方法来访问阵列中的点: Point2f point = pointsMat.at<Point2f>(i, 0); 内存管理与引用计数 Mat 是一个用来保存阵列/图像特征的数据结构(包含行列编号、数据类型等),同时包含了指向数据的指针。因此同一个数据我们可以有多个 Mat 实例。一个 Mat 实例保存了一个数据的引用计数,当 Mat 实例被销毁时引用计数会减一。下面是一个无需拷贝数据的情况下创建两个矩阵的示例代码: std::vector<Point3f> points; // .. fill the array Mat pointsMat = Mat(points).reshape(1); 上述结果返回一个 32FC1 的矩阵,包含 3 列数据,而不是 32FC3 的 1 列数据。pointsMat 使用这些点数据,在销毁的时候不会释放内存。在这个特定的情况下,开发者必须确保点阵的生命周期比 pointsMat 要长才行。如果我们需要复制数据,可以使用 Mat::copyTo 或者 Mat::clone: Mat img = imread("image.jpg"); Mat img1 = img.clone(); 与开发人员所创建的输出图像的相反,一个空的输出 Mat 可以提供给每个函数。该方法为一个空的矩阵分配数据。如果矩阵的数据和类型都无误,则该方法什么都不做。如果输入参数的大小和类型不一致,该数据就会被释放(并丢失)然后分配新的数据,例如: Mat img = imread("image.jpg"); Mat sobelx; Sobel(img, sobelx, CV_32F, 1, 0); 基本操作 一个阵列有很多的基本操作。例如我们可以从一个已有的灰度图总生成一个黑色图像:: img = Scalar(0); Selecting a region of interest: Rect r(10, 10, 100, 100); Mat smallImg = img(r); 将 Mat 转成 C API 数据结构: Mat img = imread("image.jpg"); IplImage img1 = img; CvMat m = img; 注意这里没有做任何的数据拷贝。 将彩色图转成灰度图: Mat img = imread("image.jpg"); // loading a 8UC3 image Mat grey; cvtColor(img, grey, CV_BGR2GRAY); 将图片类型从 8UC1 改为 32FC1: src.convertTo(dst, CV_32F); 图像显示 在开发过程中看到通过你算法出来的图片是很爽的。OpenCV 提供了一个便捷的方式来显示图像。一个8U图像可以使用如下代码显示: Mat img = imread("image.jpg"); namedWindow("image", CV_WINDOW_AUTOSIZE); imshow("image", img); waitKey(); waitKey() 方法调用开始一个消息的传递循环,等待用户在图像窗口中按键。32F 的图像需要转成 8U 才可以显示,例如: Mat img = imread("image.jpg"); Mat grey; cvtColor(img, grey, CV_BGR2GRAY); Mat sobelx; Sobel(grey, sobelx, CV_32F, 1, 0); double minVal, maxVal; minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities Mat draw; sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal)); namedWindow("image", CV_WINDOW_AUTOSIZE); imshow("image", draw); waitKey();
OpenCV 判断点是否在多边形内 目的 在这个教程中我们将学习如何使用 OpenCV 函数 pointPolygonTest 代码 详细代码如下 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace cv; using namespace std; /** @function main */ int main( int argc, char** argv ) { /// Create an image const int r = 100; Mat src = Mat::zeros( Size( 4*r, 4*r ), CV_8UC1 ); /// Create a sequence of points to make a contour: vector<Point2f> vert(6); vert[0] = Point( 1.5*r, 1.34*r ); vert[1] = Point( 1*r, 2*r ); vert[2] = Point( 1.5*r, 2.866*r ); vert[3] = Point( 2.5*r, 2.866*r ); vert[4] = Point( 3*r, 2*r ); vert[5] = Point( 2.5*r, 1.34*r ); /// Draw it in src for( int j = 0; j < 6; j++ ) { line( src, vert[j], vert[(j+1)%6], Scalar( 255 ), 3, 8 ); } /// Get the contours vector<vector<Point> > contours; vector<Vec4i> hierarchy; Mat src_copy = src.clone(); findContours( src_copy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); /// Calculate the distances to the contour Mat raw_dist( src.size(), CV_32FC1 ); for( int j = 0; j < src.rows; j++ ) { for( int i = 0; i < src.cols; i++ ) { raw_dist.at<float>(j,i) = pointPolygonTest( contours[0], Point2f(i,j), true ); } } double minVal; double maxVal; minMaxLoc( raw_dist, &minVal, &maxVal, 0, 0, Mat() ); minVal = abs(minVal); maxVal = abs(maxVal); /// Depicting the distances graphically Mat drawing = Mat::zeros( src.size(), CV_8UC3 ); for( int j = 0; j < src.rows; j++ ) { for( int i = 0; i < src.cols; i++ ) { if( raw_dist.at<float>(j,i) < 0 ) { drawing.at<Vec3b>(j,i)[0] = 255 - (int) abs(raw_dist.at<float>(j,i))*255/minVal; } else if( raw_dist.at<float>(j,i) > 0 ) { drawing.at<Vec3b>(j,i)[2] = 255 - (int) raw_dist.at<float>(j,i)*255/maxVal; } else { drawing.at<Vec3b>(j,i)[0] = 255; drawing.at<Vec3b>(j,i)[1] = 255; drawing.at<Vec3b>(j,i)[2] = 255; } } } /// Create Window and show your results char* source_window = "Source"; namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); namedWindow( "Distance", CV_WINDOW_AUTOSIZE ); imshow( "Distance", drawing ); waitKey(0); return(0); } Result
OpenCV 通过 Features2D 和 Homography 查找已知对象 目标 本文中你将学会: 使用 findHomography 函数来查找匹配关键点之间的转换 使用 perspectiveTransform 来映射点 代码 完整的代码可从这里 下载 #include <stdio.h> #include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/calib3d/calib3d.hpp" #include "opencv2/nonfree/nonfree.hpp" using namespace cv; void readme(); /** @function main */ int main( int argc, char** argv ) { if( argc != 3 ) { readme(); return -1; } Mat img_object = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_scene = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ); if( !img_object.data || !img_scene.data ) { std::cout<< " --(!) Error reading images " << std::endl; return -1; } //-- Step 1: Detect the keypoints using SURF Detector int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_object, keypoints_scene; detector.detect( img_object, keypoints_object ); detector.detect( img_scene, keypoints_scene ); //-- Step 2: Calculate descriptors (feature vectors) SurfDescriptorExtractor extractor; Mat descriptors_object, descriptors_scene; extractor.compute( img_object, keypoints_object, descriptors_object ); extractor.compute( img_scene, keypoints_scene, descriptors_scene ); //-- Step 3: Matching descriptor vectors using FLANN matcher FlannBasedMatcher matcher; std::vector< DMatch > matches; matcher.match( descriptors_object, descriptors_scene, matches ); double max_dist = 0; double min_dist = 100; //-- Quick calculation of max and min distances between keypoints for( int i = 0; i < descriptors_object.rows; i++ ) { double dist = matches[i].distance; if( dist < min_dist ) min_dist = dist; if( dist > max_dist ) max_dist = dist; } printf("-- Max dist : %f \n", max_dist ); printf("-- Min dist : %f \n", min_dist ); //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist ) std::vector< DMatch > good_matches; for( int i = 0; i < descriptors_object.rows; i++ ) { if( matches[i].distance < 3*min_dist ) { good_matches.push_back( matches[i]); } } Mat img_matches; drawMatches( img_object, keypoints_object, img_scene, keypoints_scene, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); //-- Localize the object std::vector<Point2f> obj; std::vector<Point2f> scene; for( int i = 0; i < good_matches.size(); i++ ) { //-- Get the keypoints from the good matches obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt ); scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt ); } Mat H = findHomography( obj, scene, CV_RANSAC ); //-- Get the corners from the image_1 ( the object to be "detected" ) std::vector<Point2f> obj_corners(4); obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 ); obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows ); std::vector<Point2f> scene_corners(4); perspectiveTransform( obj_corners, scene_corners, H); //-- Draw lines between the corners (the mapped object in the scene - image_2 ) line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 ); line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 ); //-- Show detected matches imshow( "Good Matches & Object detection", img_matches ); waitKey(0); return 0; } /** @function readme */ void readme() { std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; } 结果 下面是检测对象的结果(绿色框内)
OpenCV 模板匹配 目的 在这篇教程中你将学会: 学会使用 OpenCV 函数 matchTemplate 来搜索两个图片之间相匹配的部分 学会使用 OpenCV 函数 minMaxLoc 在给定的数组中查找最大和最小值(以及位置). 原理 什么是模板匹配? 模板匹配是一项在给定的图片中查找模板图片的技术。. 模板匹配是怎么工作的? 上面的图片通过滑动匹配一个 TM_CCORR_NORMED 并得到 R 结果。最高匹配值显示为最佳匹配。如你所见,红色线条标识出来的是最高值,因此该位置(矩形)就是匹配的结果。 通过模板图片的滑动,这意味着模板图片每次滑动一个像素(从左到右,从上到下)。每个不同的位置上,都会计算出一个数值来表示匹配度。 实际上我们可以使用函数 minMaxLoc 来定位在 R 阵列中最高值(或者最低值,这取决于匹配方法的类型). OpenCV 提供哪些匹配方法? OpenCV 通过 matchTemplate 来实现模板匹配,提供了以下 6 种方法: method=CV_TM_SQDIFF method=CV_TM_SQDIFF_NORMED method=CV_TM_CCORR method=CV_TM_CCORR_NORMED method=CV_TM_CCOEFF where method=CV_TM_CCOEFF_NORMED 代码 本程序主要功能: 加载图片以及要匹配的模板图片 使用 OpenCV 的 matchTemplate 函数执行匹配过程,并使用 6 种方法进行试验 对匹配输出进行规划化处理 定位匹配度最高的位置 在最高匹配的区域绘制矩形框 下载代码请点击 这里 详细代码: #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace std; using namespace cv; /// Global Variables Mat img; Mat templ; Mat result; char* image_window = "Source Image"; char* result_window = "Result window"; int match_method; int max_Trackbar = 5; /// Function Headers void MatchingMethod( int, void* ); /** @function main */ int main( int argc, char** argv ) { /// Load image and template img = imread( argv[1], 1 ); templ = imread( argv[2], 1 ); /// Create windows namedWindow( image_window, CV_WINDOW_AUTOSIZE ); namedWindow( result_window, CV_WINDOW_AUTOSIZE ); /// Create Trackbar char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED"; createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod ); MatchingMethod( 0, 0 ); waitKey(0); return 0; } /** * @function MatchingMethod * @brief Trackbar callback */ void MatchingMethod( int, void* ) { /// Source image to display Mat img_display; img.copyTo( img_display ); /// Create the result matrix int result_cols = img.cols - templ.cols + 1; int result_rows = img.rows - templ.rows + 1; result.create( result_rows, result_cols, CV_32FC1 ); /// Do the Matching and Normalize matchTemplate( img, templ, result, match_method ); normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() ); /// Localizing the best match with minMaxLoc double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc; minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); /// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED ) { matchLoc = minLoc; } else { matchLoc = maxLoc; } /// Show me what you got rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); imshow( image_window, img_display ); imshow( result_window, result ); return; } 代码解释 定义全局变量,注入图像、模板、结果阵列等等: Mat img; Mat templ; Mat result; char* image_window = "Source Image"; char* result_window = "Result window"; int match_method; int max_Trackbar = 5; 加载原图和模板图: img = imread( argv[1], 1 ); templ = imread( argv[2], 1 ); 创建窗体来显示结果: namedWindow( image_window, CV_WINDOW_AUTOSIZE ); namedWindow( result_window, CV_WINDOW_AUTOSIZE ); 创建 Trackbar 给匹配方法使用,当有变化时候回调 MatchingMethod 方法. char* trackbar_label = "Method: \n 0: SQDIFF \n 1: SQDIFF NORMED \n 2: TM CCORR \n 3: TM CCORR NORMED \n 4: TM COEFF \n 5: TM COEFF NORMED"; createTrackbar( trackbar_label, image_window, &match_method, max_Trackbar, MatchingMethod ); 等待用户键入任何键以退出程序. waitKey(0); return 0; 检查回调函数,首先对原图做了个拷贝: Mat img_display; img.copyTo( img_display ); 下一步创建结果阵列用来存储匹配结果,观察结果阵列的大小 int result_cols = img.cols - templ.cols + 1; int result_rows = img.rows - templ.rows + 1; result.create( result_rows, result_cols, CV_32FC1 ); 执行模板匹配函数: matchTemplate( img, templ, result, match_method ); 参数包含输入图片、模板图片,结果存放阵列以及匹配方法 对结果进行规范化: normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() ); 使用 minMaxLoc 在结果阵列中定位最大和最小的匹配度 double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc; minMaxLoc( result, &minVal, &maxVal, &minLoc, &maxLoc, Mat() ); 该函数需要以下参数: result: 源数组 &minVal 和 &maxVal: 用来存储结果中的最小和最大匹配度 &minLoc and &maxLoc: 数组中的最小和最大匹配对应的位置点. Mat(): 可选的掩码值 对前两个方法 ( CV_SQDIFF 和 CV_SQDIFF_NORMED ) 来说最低值表示最佳匹配。而其他的方法则是值越高匹配度越高。因此我们在 matchLoc 变量中保存相应的值: if( match_method == CV_TM_SQDIFF || match_method == CV_TM_SQDIFF_NORMED ) { matchLoc = minLoc; } else { matchLoc = maxLoc; } 显示原图以及结果阵列,并在最高匹配的位置绘制矩形:: rectangle( img_display, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); rectangle( result, matchLoc, Point( matchLoc.x + templ.cols , matchLoc.y + templ.rows ), Scalar::all(0), 2, 8, 0 ); imshow( image_window, img_display ); imshow( result_window, result ); 匹配结果 测试程序的原图: 测试程序的模板图: 生成下列的结果图(前面使用的是标准方法: SQDIFF, CCORR 和 CCOEFF, 下面是经过规范化后的版本). 前三个图里,第一个图最黑表示最佳匹配,第2、3个图则是最亮为最佳匹配。 正确的匹配结果如下图所示 (黑色方框为匹配区域)。需要注意的是 CCORR 和 CCDEFF 给出了错误的匹配,但是这些错误的匹配经过规范化后结果就正确了。这是因为我们使用了最高匹配而不是可能的高匹配。
OpenCV 显示图像的凸包 Convex Hull 效果 目的 本文将教你如何使用 OpenCV 函数 convexHull 代码 代码如下所示,可从这里 下载 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace cv; using namespace std; Mat src; Mat src_gray; int thresh = 100; int max_thresh = 255; RNG rng(12345); /// Function header void thresh_callback(int, void* ); /** @function main */ int main( int argc, char** argv ) { /// Load source image and convert it to gray src = imread( argv[1], 1 ); /// Convert image to gray and blur it cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) ); /// Create Window char* source_window = "Source"; namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); createTrackbar( " Threshold:", "Source", &thresh, max_thresh, thresh_callback ); thresh_callback( 0, 0 ); waitKey(0); return(0); } /** @function thresh_callback */ void thresh_callback(int, void* ) { Mat src_copy = src.clone(); Mat threshold_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// Detect edges using Threshold threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY ); /// Find contours findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// Find the convex hull object for each contour vector<vector<Point> >hull( contours.size() ); for( int i = 0; i < contours.size(); i++ ) { convexHull( Mat(contours[i]), hull[i], false ); } /// Draw contours + hull results Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() ); drawContours( drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point() ); } /// Show in a window namedWindow( "Hull demo", CV_WINDOW_AUTOSIZE ); imshow( "Hull demo", drawing ); } 结果
OpenCV 特征描述 目标 本文将讲述的内容包括: 使用 DescriptorExtractor 接口来查找关键点的特征向量。特别是: 使用 SurfDescriptorExtractor 和其函数 compute 来执行所需的计算. 使用 BFMatcher 来匹配特征向量 使用函数 drawMatches 来绘制检测的结果. 代码 完整代码可以从这里 下载 #include <stdio.h> #include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/nonfree/features2d.hpp" using namespace cv; void readme(); /** @function main */ int main( int argc, char** argv ) { if( argc != 3 ) { return -1; } Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ); if( !img_1.data || !img_2.data ) { return -1; } //-- Step 1: Detect the keypoints using SURF Detector int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_1, keypoints_2; detector.detect( img_1, keypoints_1 ); detector.detect( img_2, keypoints_2 ); //-- Step 2: Calculate descriptors (feature vectors) SurfDescriptorExtractor extractor; Mat descriptors_1, descriptors_2; extractor.compute( img_1, keypoints_1, descriptors_1 ); extractor.compute( img_2, keypoints_2, descriptors_2 ); //-- Step 3: Matching descriptor vectors with a brute force matcher BFMatcher matcher(NORM_L2); std::vector< DMatch > matches; matcher.match( descriptors_1, descriptors_2, matches ); //-- Draw matches Mat img_matches; drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches ); //-- Show detected matches imshow("Matches", img_matches ); waitKey(0); return 0; } /** @function readme */ void readme() { std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; } 结果
OpenCV 查找图像轮廓 目的 我们将学习: 学会使用 OpenCV 函数 findContours 学会使用 OpenCV 函数 drawContours 原理 代码 下面是本示例的代码,可以从这里 下载 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace cv; using namespace std; Mat src; Mat src_gray; int thresh = 100; int max_thresh = 255; RNG rng(12345); /// Function header void thresh_callback(int, void* ); /** @function main */ int main( int argc, char** argv ) { /// Load source image and convert it to gray src = imread( argv[1], 1 ); /// Convert image to gray and blur it cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) ); /// Create Window char* source_window = "Source"; namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback ); thresh_callback( 0, 0 ); waitKey(0); return(0); } /** @function thresh_callback */ void thresh_callback(int, void* ) { Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// Detect edges using canny Canny( src_gray, canny_output, thresh, thresh*2, 3 ); /// Find contours findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// Draw contours Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() ); } /// Show in a window namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); imshow( "Contours", drawing ); } 解释 结果
OpenCV 图像力矩 目的 本文将带你学习: 使用 OpenCV 函数 moments 使用 OpenCV 函数 contourArea 使用 OpenCV 函数 arcLength 代码 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace cv; using namespace std; Mat src; Mat src_gray; int thresh = 100; int max_thresh = 255; RNG rng(12345); /// Function header void thresh_callback(int, void* ); /** @function main */ int main( int argc, char** argv ) { /// Load source image and convert it to gray src = imread( argv[1], 1 ); /// Convert image to gray and blur it cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) ); /// Create Window char* source_window = "Source"; namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src ); createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback ); thresh_callback( 0, 0 ); waitKey(0); return(0); } /** @function thresh_callback */ void thresh_callback(int, void* ) { Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// Detect edges using canny Canny( src_gray, canny_output, thresh, thresh*2, 3 ); /// Find contours findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); /// Get the moments vector<Moments> mu(contours.size() ); for( int i = 0; i < contours.size(); i++ ) { mu[i] = moments( contours[i], false ); } /// Get the mass centers: vector<Point2f> mc( contours.size() ); for( int i = 0; i < contours.size(); i++ ) { mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); } /// Draw contours Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 ); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() ); circle( drawing, mc[i], 4, color, -1, 8, 0 ); } /// Show in a window namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); imshow( "Contours", drawing ); /// Calculate the area with the moments 00 and compare with the result of the OpenCV function printf("\t Info: Area and Contour Length \n"); for( int i = 0; i< contours.size(); i++ ) { printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength( contours[i], true ) ); Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) ); drawContours( drawing, contours, i, color, 2, 8, hierarchy, 0, Point() ); circle( drawing, mc[i], 4, color, -1, 8, 0 ); } } 运行结果
OpenCV 使用 FLANN 库实现特征匹配 目标 在这篇文章中你将学到: 使用 FlannBasedMatcher 接口来执行快速高效的匹配,用的是 FLANN ( Fast Approximate Nearest Neighbor Search Library ) 算法 代码 完整代码可从这里 下载 /** * @file SURF_FlannMatcher * @brief SURF detector + descriptor + FLANN Matcher * @author A. Huaman */ #include "opencv2/opencv_modules.hpp" #include <stdio.h> #ifndef HAVE_OPENCV_NONFREE int main(int, char**) { printf("The sample requires nonfree module that is not available in your OpenCV distribution.\n"); return -1; } #else # include "opencv2/core/core.hpp" # include "opencv2/features2d/features2d.hpp" # include "opencv2/highgui/highgui.hpp" # include "opencv2/nonfree/features2d.hpp" using namespace cv; void readme(); /** * @function main * @brief Main function */ int main( int argc, char** argv ) { if( argc != 3 ) { readme(); return -1; } Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ); if( !img_1.data || !img_2.data ) { printf(" --(!) Error reading images \n"); return -1; } //-- Step 1: Detect the keypoints using SURF Detector int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_1, keypoints_2; detector.detect( img_1, keypoints_1 ); detector.detect( img_2, keypoints_2 ); //-- Step 2: Calculate descriptors (feature vectors) SurfDescriptorExtractor extractor; Mat descriptors_1, descriptors_2; extractor.compute( img_1, keypoints_1, descriptors_1 ); extractor.compute( img_2, keypoints_2, descriptors_2 ); //-- Step 3: Matching descriptor vectors using FLANN matcher FlannBasedMatcher matcher; std::vector< DMatch > matches; matcher.match( descriptors_1, descriptors_2, matches ); double max_dist = 0; double min_dist = 100; //-- Quick calculation of max and min distances between keypoints for( int i = 0; i < descriptors_1.rows; i++ ) { double dist = matches[i].distance; if( dist < min_dist ) min_dist = dist; if( dist > max_dist ) max_dist = dist; } printf("-- Max dist : %f \n", max_dist ); printf("-- Min dist : %f \n", min_dist ); //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist, //-- or a small arbitary value ( 0.02 ) in the event that min_dist is very //-- small) //-- PS.- radiusMatch can also be used here. std::vector< DMatch > good_matches; for( int i = 0; i < descriptors_1.rows; i++ ) { if( matches[i].distance <= max(2*min_dist, 0.02) ) { good_matches.push_back( matches[i]); } } //-- Draw only "good" matches Mat img_matches; drawMatches( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); //-- Show detected matches imshow( "Good Matches", img_matches ); for( int i = 0; i < (int)good_matches.size(); i++ ) { printf( "-- Good Match [%d] Keypoint 1: %d -- Keypoint 2: %d \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); } waitKey(0); return 0; } /** * @function readme */ void readme() { printf(" Usage: ./SURF_FlannMatcher <img1> <img2>\n"); } #endif 结果 这是对首张图片进行特征检测的结果 下面是对关键点进行过滤过程中的控制台输出: 来源:docs.opencv.org [英文]
Opencv 英文文档地址 : docs.opencv.org OpenCV (Open Source Computer Vision Library: http://opencv.org) 是一个使用 BSD 许可证的开源库,包含数百个计算机视觉算法。此文档详细的描述了 OpenCV 2.x API,这主要是 C++ API,相对于 OpenCV 1.x API 的 C API。 OpenCV 使用模块化的结构,这表明其包含很多共享或者静态库。OpenCV 提供如下模块: core - 这是一个定义了基本数据结构的紧凑模块,包含大量被其他模块调用的多维的 Mat 和其他基本函数。 imgproc - 这是一个图像处理模块,包含线性和非线性的图像过滤,几何图形转换 (大小调整, 仿射和透视扭曲, 通用的基于表的重新映射), 色彩空间转换以及直方图等等。 video - 这是一个视频分析模块,包含运动评估、背景提取以及对象跟踪算法。 calib3d - 基本的多视图几何算法,实现单个和立体的摄像头校准,对象构成的评估、立体相关算法和一些 3D 构造算法。 features2d - 突出特征检测、描述以及描述器的匹配。 objdetect - 对象检测以及预定义类的检测,例如人脸、眼睛、杯子、人物、汽车等等。 highgui - 一个易用的视频捕获、图像和视频编码接口,同时提供简单的图形化界面。 gpu - 来自不同 OpenCV 模块的 GPU 加速算法。 ... 其他一些助手模块,例如 FLANN 和 Google 测试封装、Python 包等等。 该文档的其他章节描述了每个模块的功能。但首先,我们需要对一些贯通整个库的通用 API 熟悉。 API 概念 cv 命名空间 所有 OpenCV 的类和函数被放到了 cv 这个命名空间。所以为了在代码中访问这些功能,需要使用 cv:: 说明符或者使用 namespace cv; 指令: #include "opencv2/core/core.hpp" ... cv::Mat H = cv::findHomography(points1, points2, CV_RANSAC, 5); ... 或者 #include "opencv2/core/core.hpp" using namespace cv; ... Mat H = findHomography(points1, points2, CV_RANSAC, 5 ); ... 当前 OpenCV 提供的一些命名可能会跟 STL 或者其他库的命名冲突,可以使用显式的指定命名空间来解决这个冲突问题,例如: Mat a(100, 100, CV_32F); randu(a, Scalar::all(1), Scalar::all(std::rand())); cv::log(a, a); a /= std::log(2.); 自动内存管理 OpenCV 的内存处理是完全自动化的。 首先 std::vector, Mat, 以及其他数据结构提供了析构函数,可在需要的时候释放底层占用内存。这意味着好比是 Mat 这个数据结构而言,析构函数并不总是会释放内存,此举是为了便于数据共享。析构函数只是减少了所关联对象的引用计数器而已。如果引用计数器数值为 0 的时候对象才会被释放,因为再没有其他结构引用到该数据。同样的,当一个 Mat 实例被拷贝,实际上并没有发送拷贝数据的操作,只是引用计数值增1来记录使用关系。当然 Mat 还提供了 Mat::clone 方法来强制进行数据拷贝。示例如下: // 创建一个大的 8Mb 的矩阵 Mat A(1000, 1000, CV_64F); // 创建该矩阵的另外一个引用 // 真实一个实例操作,无视阵列大小 Mat B = A; // 为 A 的第三行创建另外一个头,此处没有数据拷贝动作 Mat C = B.row(3); // 现在创建一个独立的阵列拷贝 Mat D = B.clone(); // 从 B 拷贝第五行数据到 C // to the 3-rd row of A. B.row(5).copyTo(C); // 让 A 和 D 共享修改后数据,注意 A 仍被 B 和 C 引用 A = D; // 现在让 B 变成空的阵列,释放内存, // 但是修改好的 A 仍被 C 引用, // 尽管 C 只是原始 A 的单行数据 B.release(); // 最,我们对 C 做一个全拷贝,这是大的改动 // 阵列将被释放,因为没有任何对象引用到它 C = C.clone(); 你会发现 Mat 和其他基础结构的使用时很简单的。但是其他一些高级类和用户自行创建的数据类型如何呢?是否也可以实现自动的内存管理呢?对于这些来说 OpenCV 提供了 Ptr<> 模板类,类似 C++ TR1 的 std::shared_ptr . 因此使用指针来替换的方法如下: T* ptr = new T(...); 你可以使用: Ptr<T> ptr = new T(...); 也就是说 Ptr<T> ptr 封装了一个指向 T 实例的指针和关联该指针的引用计数,详情请看 Ptr 的详细描述。 输出数据的自动分配 OpenCV 会自动释放内存,就如同大多数时候为输出函数的参数自动分配内存一样。因此,如果一个函数有一个或者多个输入的数组 (cv::Mat 实例) 和一些输出数组,输出的数组会实现自动的内存分配和释放。输出数组的大小和类型会根据输入数组的大小和类型来自动识别。如果需要的话函数可以提供额外的参数来帮助设定输出数组的属性。 示例代码: #include "cv.h" #include "highgui.h" using namespace cv; int main(int, char**) { VideoCapture cap(0); if(!cap.isOpened()) return -1; Mat frame, edges; namedWindow("edges",1); for(;;) { cap >> frame; cvtColor(frame, edges, CV_BGR2GRAY); GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5); Canny(edges, edges, 0, 30, 3); imshow("edges", edges); if(waitKey(30) >= 0) break; } return 0; } 一旦视频捕获模块解析到视频帧以及位深度,那么帧数组会自动通过 >> 操作符进行分配。数组的边界是通过 cvtColor 函数自动分配的。它跟输入的数组具有相同的大小和位深度。因为传递了 CV_BGR2GRAY 参数给色彩转换代码,因此通道数是 1。需要注意的是帧和边界只在首次执行时候分配一次,因此紧接着的所有视频帧都具有相同的分辨率。如果你以某种方式改变了视频分辨率,那么数组就会自动重新分配。 此技术的关键组件是 Mat::create 方法,需要指定数组的大小和类型。如果数组已经有指定的大小和类型了,那么该方法什么都不做。否则它会释放之前已分配的数据,如果有的话(这一部分设计到引用计数减一并判断是否为0)然后分配一个新的缓冲区以满足数据要求。大多数函数为每个输出的数组调用 Mat::create 方法来创建,因此实现了输出数据分配的自动化。 一些值得注意的例外是 cv::mixChannels 和 cv::RNG::fill ,同时还有其他的函数和方法。他们不会分配输出数组,你必须在调用之前进行分配。 饱和算法 作为一个计算机视觉库,OpenCV 处理大量的图像像素,这些都是使用紧缩的每通道、形式或者值范围的 8或者16位的像素。此外图像上的特定操作,例如色彩空间转换、亮度对比度调整、锐化以及其他复杂的操作 (bi-cubic, Lanczos) 会产生超出范围的值。如果你只是存储最低的 8或者16位值,这会导致视觉上的伪影,从而影响进一步的图像分析。为了解决这个问题,我们可以使用所谓的“饱和算法”。例如,为了存储操作的结果 r 到一个 8 位的图像,你可以查找 0 到 255 中最ji,can produce values out of the available range. If you just store the lowest 8 (16) bits of the result, this results in visual artifacts and may affect a further image analysis. To solve this problem, the so-called saturation arithmetics is used. For example, to store r, the result of an operation, to an 8-bit image, you find the nearest value within the 0..255 range: 带符号的 8 位、16类型以及不带符号的类型也使用类似的规则,在整个库的 C++ 代码都使用这个语义规则。可使用 saturate_cast<> 函数来实现类似标准 C++ 的 cast 操作。下面一行代码实现了上图中的计算公式: I.at<uchar>(y, x) = saturate_cast<uchar>(r); 因为 cv::uchar 是一个 OpenCV 8-bit 无符号整数值,因此在优化的 SIMD 代码,例如 SSE2 指令:paddusb, packuswb 等等就会被使用到。这实现了与 C++ 代码类似的相同行为。 注意 当结果是 32位整数时,是无法用到 Saturation 饱和的(译者注:此句该做何解?)。 固定像素类型,模板使用限制 模板是 C++ 一个非常棒的特性之一,可以实现非常强大、高效以及安全的数据结构和算法。但是大量使用模板会戏剧性的增加编译时间以及代码的体积。除此之外,很难分离一个模板的接口以及相应的实现。这用来做一些基本的算法是挺好的,但是对于计算机视觉库这样可能包含数千行代码的复杂工作就不太合适。因为这个同时需要提供其他语言的支持版本,而像 Python、Java、Matlab 等编程语言并没有模板的概念,就会导致功能受限。当前的 OpenCV 实现是基于多态以及模板之上的运行时调度。这会导致运行时调度变得非常慢(例如像素访问操作)以及无法运行(泛型 Ptr<> 实现),以及可能非常不方便(saturate_cast<>()),因此当前实现使用了小的模板类、方法和函数。在当前的 OpenCV 版本中模板的使用都是受限的。 因此,对于一些可操作的基本类型来说是有一些固定的限制。也就是说,数组元素必须是如下罗列的类型中的其中一个: 8-bit 无符号整数 (uchar) 8-bit 有符号整数 (schar) 16-bit 无符号整数 (ushort) 16-bit 有符号整数 (short) 32-bit 有符号整数 (int) 32-bit 浮点数(float) 64-bit 浮点数 (double) 一组多元素的元组,但所有元素的类型必须一致,而且必须是上面几种类型。数组的元素如果是元组,相当于是多通道数组,与单通道数组也跟元组类型相反,这些元素必须是标量类型。最大的通道数是定义为 CV_CN_MAX 的常量值,当前是 512. 对于这些基本类型,OpenCV 提供了如下枚举与之对应: enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; 多通道 (n-channel) 类型可通过如下的选项进行指定: CV_8UC1 ... CV_64FC4 常量 (对应 1 到 4 的通道编号) CV_8UC(n) ... CV_64FC(n) 或者 CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n) 宏,当通道数量超过 4 或者未知时 注意 CV_32FC1 == CV_32F, CV_32FC2 == CV_32FC(2) == CV_MAKETYPE(CV_32F, 2), 以及 CV_MAKETYPE(depth, n) == (depth&7) + ((n-1)<<3). 意思是常量类型是根据深度形成的。占用了低位 3 比特,此外通道数量减1占用下一个 log2(CV_CN_MAX) 比特. 示例: Mat mtx(3, 3, CV_32F); // 生成一个 3x3 浮点阵列 Mat cmtx(10, 1, CV_64FC2); // 生成一个 10x1 2通道浮点 // 阵列 (10元素的复杂向量) Mat img(Size(1920, 1080), CV_8UC3); // 生成一个三通道的彩色图像 // 包含 1920 列和 1080 行. Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); // 生成一个单通道的相同大小和通道类型的图像 包含更复杂元素的数组没法使用 OpenCV 进行构建和处理。此外,每个函数或者方法只能处理任何可能数组类型的一个子集。通常,算法越复杂,支持的格式子集就越小。下面是这些限制的一些典型示例: 人脸识别算法只支持 8 位灰度图以及彩色图. 线性代数函数以及大多数的机器学习算法只支持浮点数数组. 基本的函数,例如 cv:add 支持所有类型. 色彩空间转换函数支持 8位无符号、16位无符号以及32位浮点数类型. 每个函数所支持的类型的子集都是为了满足实际的需要,并且可根据用户的需求在将来进行扩展。 InputArray 和 OutputArray 很多 OpenCV 函数密集的处理2维以及多维的数组,例如使用 cpp:class:Mat 作为参数的函数,但是在某些情况下使用 std::vector<> 或者 Matx<> 更方便(例如一个点阵集合)。为了避免 API 中很多重复的代码,OpenCV 专门引入了一个 “proxy”类。一个基本的 “proxy”类就是 InputArray. 它被用来传递只读数组。而 InputArray 的派生类 OutputArray 用来在函数中指定输出数组。一般情况下你不需要关心这些中间类型(你也不能显式的定义这种类型的变量),它们都是自动被处理的。你可以假设在使用 Mat、std::vector<>、Matx<>、Vec<> 以及一些标量类型的时候会自动替换成 InputArray/OutputArray 。当一个函数包含一个可选的输入和输出数组时,你没有这样的参数也不想要有,可以传递 cv::noArray() 。 错误处理 OpenCV 使用异常来表示关键错误。当输入的数据包含正确的格式以及属于指定的值范围,但是算法因为某种原因无法正确处理时就会返回特定的错误码(一般是一个布尔值变量)。 这些异常可以是 cv::Exception 类或者派生类的实例。此外 cv::Exception 是 std::exception 的派生类。因此可以使用标准的 C++ 库组件来处理这些异常。 异常是通过 CV_Error(errcode, description) 宏抛出来的,或者可以使用类 printf 函数风格的方法变种 CV_Error_(errcode, printf-spec, (printf-args)) ,或者使用断言宏 CV_Assert(condition) 检查各种条件,并在不满足的情况下抛出异常。如果你对性能非常在意的话,可以使用 CV_DbgAssert(condition) 方法,该方法只在 Debug 模式下有效。因为自动内存管理的原因,所有在发生错误时所产生的中间缓冲区会被自动的释放。你只需要在需要的时候添加 try 语句和 catch 异常即可。 try { ... // call OpenCV } catch( cv::Exception& e ) { const char* err_msg = e.what(); std::cout << "exception caught: " << err_msg << std::endl; } 多线程和可重入 当前的 OpenCV 版本是完全支持可重入的,这就是说相同的函数、类实例的 constant 方法或者是不同类实例的相同 non-constant 方法可以在不同的线程中调用。同时,相同的 cv::Mat 也可以在不同的线程中使用,因为这里有引用计数来实现特定架构的原子操作。
1.打开Cydia,选择“开发者”2.在Cydia里搜索安装OpenSSH,apt0.6 transitional,iFile3.查找iPhone连接wifi后,获取到的IP 准备好后,使用ssh命令连接iPhone;IOS系统默认的root密码为alpine,要及时修改哦: $ ssh root@192.168.1.100root@192.168.2.146 s password: X11 forwarding request failed on channel 0yournamede-iPhone:~ root# uname -aDarwin yournamede-iPhone 14.0.0 Darwin Kernel Version 14.0.0: Fri Sep 27 23:07:56 PDT 2013;root:xnu-2423.3.12~1/RELEASE_ARM_S5L8930X iPhone3,1 arm N90AP Darwin</p> <p>yournamede-iPhone:~ root# df -hFilesystem Size Used Avail Use% Mounted on/dev/disk0s1s1 1.4G 1.1G 300M 78% /devfs 35K 35K 0 100% /dev/dev/disk0s1s2 14G 14G 237M 99% /private/var</p> <p>yournamede-iPhone:~ root# passwd rootChanging password for root.</p> <p>younamede-iPhone:~ root# ls /Applications Developer Library System User bin boot cores dev etc evasi0n7 evasi0n7-installed lib mnt private sbin tmp usr var 用apt-get安装常用的命令: yournamede-iPhone:~ root# apt-get install ping netstat wget vim screen 查看iPhone已建立的连接: yournamede-iPhone:~ root# netstat -an | grep "ESTABLISHED" tcp4 0 0 192.168.1.100.22 192.168.1.53.34074 ESTABLISHEDtcp4 0 0 192.168.1.100.53524 17.110.224.207.443 ESTABLISHEDtcp4 0 0 10.75.179.182.53521 17.110.226.8.5223 ESTABLISHED 用top命令查看iPhone进程信息: yournamede-iPhone:~ root# apt-get install topyournamede-iPhone:~ root# top 直接在iPhone中下载文档,并用iFile来管理 这是我最喜爱的操作,简直是方便极了: iPhone:~ root# cd /var/mobile/Documents/iPhone:/var/mobile/Documents root# wget -c ftp://free:6LHnC1OP@www.ppurl.com/u/30/4404730/4404730.5132173376.pdf--2014-08-20 14:20:18-- ftp://free:*password*@www.ppurl.com/u/30/4404730/4404730.5132173376.pdf=> `4404730.5132173376.pdfResolving www.ppurl.com... 222.73.57.56Connecting to www.ppurl.com|222.73.57.56|:21... connected.Logging in as free ... Logged in!==> SYST ... done. ==> PWD ... done.==> TYPE I ... done. ==> CWD /u/30/4404730 ... done.==> SIZE 4404730.5132173376.pdf ... 14818040==> PASV ... done. ==> RETR 4404730.5132173376.pdf ... done. Length: 14818040 (14M)</p> <p>100%[===========================================================================>] 14,818,040 180K/s in 49s </p> <p>2014-08-20 14:21:43 (292 KB/s) - 4404730.5132173376.pdf saved [14818040] 在iPhone中打开iFile,找到Documents/4404730.5132173376.pdf,即可进行重命名,浏览,移动,删除等操作。 本地文件用scp命令传输到iPhone $ scp Documents/Linux\ Command\ Line.pdf root@192.168.1.100:/var/mobile/Documents/ root@192.168.1.100 s password: Linux Command Line.pdf 100% 2070KB 2.0MB/s 00:00 把文件传输到iPhone指定的应用程序中,例如把视频传输至AVPlayer 1. 在iPhone上安装ps命令: yournamede-iPhone:~ root# apt-get install ps 2. 在iPhone上查找AVPlayer进程: yournamede-iPhone:~ root# ps aux | grep AVPlayer root 8715 2.9 0.1 338608 528 s000 S+ 2:43PM 0:00.01 grep AVPlayer mobile 8710 0.0 2.3 411308 11652 ?? Ss 2:42PM 0:00.99 /var/mobile/Applications/54388E91-B1D7-45DE-B42E-BF88DFC513C8/AVPlayer.app/AVPlayer</p> <p>`/var/mobile/Applications/54388E91-B1D7-45DE-B42E-BF88DFC513C8/`这个路径即AVPlayer的应用程序的根路径 3. 在本地用scp命令把视频传到AVPlayer的应用程序的Documents下: $ scp dream.flv root@192.168.1.146:/var/mobile/Applications/54388E91-B1D7-45DE-B42E-BF88DFC513C8/Documents root@192.168.1.100 s password: dream.flv 100% 6923KB 2.3MB/s 00:03
// 运行时,这整的 void SwizzleClassMethod(Class c,SEL orig, SEL new) { Method origMethod =class_getClassMethod(c, orig); Method newMethod =class_getClassMethod(c, new); c = object_getClass((id)c); if(class_addMethod(c, orig,method_getImplementation(newMethod),method_getTypeEncoding(newMethod))) class_replaceMethod(c, new,method_getImplementation(origMethod),method_getTypeEncoding(origMethod)); else method_exchangeImplementations(origMethod, newMethod); } void SwizzleInstanceMethod(Class c,SEL orig, SEL new) { Method origMethod =nil, newMethod = nil; origMethod = class_getInstanceMethod(c, orig); newMethod = class_getInstanceMethod(c, new); if ((origMethod !=nil) && (newMethod != nil)) { if(class_addMethod(c, orig,method_getImplementation(newMethod),method_getTypeEncoding(newMethod))) class_replaceMethod(c, new,method_getImplementation(origMethod),method_getTypeEncoding(origMethod)); else method_exchangeImplementations(origMethod, newMethod); } else NSLog(@"Attempt to swizzle nonexistent methods!"); } void SwizzleInstanceMethodWithAnotherClass(Class c1,SEL orig, Class c2, SEL new) { Method origMethod =nil, newMethod = nil; origMethod = class_getInstanceMethod(c1, orig); newMethod = class_getInstanceMethod(c2, new); if ((origMethod !=nil) && (newMethod != nil)) { if(class_addMethod(c1, orig,method_getImplementation(newMethod),method_getTypeEncoding(newMethod))) class_replaceMethod(c1, new,method_getImplementation(origMethod),method_getTypeEncoding(origMethod)); else method_exchangeImplementations(origMethod, newMethod); } else NSLog(@"Attempt to swizzle nonexistent methods!"); } void InjectClassMethodFromAnotherClass(Class toClass, Class fromClass,SEL fromSelector, SEL toSeletor) { Method method =class_getClassMethod(fromClass, fromSelector); if (method !=nil) { if (!class_addMethod(toClass, toSeletor,method_getImplementation(method),method_getTypeEncoding(method))) NSLog(@"Attempt to add method failed"); } else NSLog(@"Attempt to add nonexistent method"); } void InjectInstanceMethodFromAnotherClass(Class toClass, Class fromClass,SEL fromSelector, SEL toSeletor) { Method method =class_getInstanceMethod(fromClass, fromSelector); if (method !=nil) { if (!class_addMethod(toClass, toSeletor,method_getImplementation(method),method_getTypeEncoding(method))) NSLog(@"Attempt to add method failed"); } else NSLog(@"Attempt to add nonexistent method"); } // 牛X 的方法,谁来弄弄 + (void)setApplicationStatusBarAlpha:(float)alpha { staticSEL selector = NULL; if (selector ==NULL) { NSString *str1 =@"rs`str"; NSString *str2 =@"A`qVhmcnv"; selector = NSSelectorFromString([[NSStringalloc] initWithFormat:@"%@%@",TGEncodeText(str1, 1),TGEncodeText(str2, 1)]); } if ([[UIApplicationsharedApplication] respondsToSelector:selector]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" UIWindow *window = [[UIApplicationsharedApplication] performSelector:selector]; #pragma clang diagnostic pop window.alpha = alpha; } } static UIView *findStatusBarView() { static Class viewClass =nil; staticSEL selector = NULL; if (selector ==NULL) { NSString *str1 =@"rs`str"; NSString *str2 =@"A`qVhmcnv"; selector = NSSelectorFromString([[NSStringalloc] initWithFormat:@"%@%@",TGEncodeText(str1, 1),TGEncodeText(str2, 1)]); viewClass = NSClassFromString(TGEncodeText(@"VJTubuvtCbs", -1)); } if ([[UIApplicationsharedApplication] respondsToSelector:selector]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" UIWindow *window = [[UIApplicationsharedApplication] performSelector:selector]; #pragma clang diagnostic pop for (UIView *subviewin window.subviews) { if ([subviewisKindOfClass:viewClass]) { return subview; } } } returnnil; } //补充上面的方法 NSString *TGEncodeText(NSString *string,int key) { NSMutableString *result = [[NSMutableStringalloc] init]; for (int i =0; i < (int)[stringlength]; i++) { unichar c = [stringcharacterAtIndex:i]; c += key; [result appendString:[NSStringstringWithCharacters:&c length:1]]; } return result; } NSString *TGStringMD5(NSString *string) { constchar *ptr = [string UTF8String]; unsignedchar md5Buffer[16]; CC_MD5(ptr, (CC_LONG)[stringlengthOfBytesUsingEncoding:NSUTF8StringEncoding], md5Buffer); NSString *output = [[NSStringalloc] initWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5Buffer[0], md5Buffer[1], md5Buffer[2], md5Buffer[3], md5Buffer[4], md5Buffer[5], md5Buffer[6], md5Buffer[7], md5Buffer[8], md5Buffer[9], md5Buffer[10], md5Buffer[11], md5Buffer[12], md5Buffer[13], md5Buffer[14], md5Buffer[15]]; return output; }
整理下iOS开发中 应用申请常用权限 #import typedef void (^AuthorizedFinishBlock)(); @interface LYAuthorizedMaster : NSObject #pragma mark - 摄像头权限 +(BOOL)checkCameraAuthority; +(void)cameraAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; #pragma mark - 麦克风权限 +(BOOL)checkAudioAuthority; +(void)audioAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; #pragma mark - 相册权限 +(BOOL)checkAlbumAuthority; +(void)albumAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; #pragma mark - 推送通知权限 +(BOOL)checkPushNotificationAuthority; +(void)pushNotificationAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; #pragma mark - 推送通知权限 +(BOOL)checkLocationAuthority; +(void)locationAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; #pragma mark - 通讯录权限 +(BOOL)checkAddressBookAuthority; +(void)AddressBookAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; 下面是.m文件 里面引入了很多库文件,也不是所用项目都会用到的,用不到的注掉就好。 #import "LYAuthorizedMaster.h" #import //摄像头麦克风 必须 #import //相册权限 #import //位置权限 #import //通讯录权限 #import "AppDelegate.h" #define kAPPName [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"] @implementation LYAuthorizedMaster #pragma mark - +(BOOL)checkAuthority:(AVAuthorizationStatus)_status{ return (_status == AVAuthorizationStatusAuthorized) || (_status == AVAuthorizationStatusNotDetermined); } +(void)showAlertController:(AuthorizedFinishBlock)_block device:(NSString *)_device{ UIAlertController *_alertC = [UIAlertController alertControllerWithTitle:@"没有权限" message:[NSString stringWithFormat:@"请开启‘%@’对 %@ 的使用权限",kAPPName,_device] preferredStyle:UIAlertControllerStyleAlert]; [_alertC addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]]; [_alertC addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; }]]; [((AppDelegate *)[UIApplication sharedApplication].delegate).window.rootViewController presentViewController:_alertC animated:YES completion:_block]; } #pragma mark - 摄像头权限 +(BOOL)checkCameraAuthority{ return [self checkAuthority:[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]]; } +(void)cameraAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail;{ if ([self checkCameraAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"相机"]; } } #pragma mark - 麦克风权限 +(BOOL)checkAudioAuthority{ return [self checkAuthority:[AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]]; } +(void)audioAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail{ if ([self checkAudioAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"麦克风"]; } } #pragma mark - 相册权限 +(BOOL)checkAlbumAuthority{ return [ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized; } +(void)albumAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail; { if ([self checkAlbumAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"照片"]; } } #pragma mark - 位置权限 +(BOOL)checkLocationAuthority { return [CLLocationManager locationServicesEnabled]; } +(void)locationAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail{ if ([self checkLocationAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"位置"]; } } #pragma mark - 推送通知权限 +(BOOL)checkPushNotificationAuthority { return [[UIApplication sharedApplication] currentUserNotificationSettings].types != UIUserNotificationTypeNone; } +(void)pushNotificationAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail{ if ([self checkAlbumAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"通知"]; } } #pragma mark - 通讯录权限 +(BOOL)checkAddressBookAuthority { return ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized || ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined; } +(void)AddressBookAuthorityCheckSuccess:(AuthorizedFinishBlock)_success fail:(AuthorizedFinishBlock)_fail{ if ([self checkAddressBookAuthority]) { if (_success) { _success(); } }else{ [self showAlertController:_fail device:@"通讯录"]; } } 最后有些时会遇到不弹出权限提示,或需要在提示框增加详细描述的时候,需要手动在info.plist加一些字段。 NSLocationWhenInUseUsageDescription 位置权限 使用期间 状态 NSLocationAlwaysUsageDescription 位置权限 始终 状态 下面这些我并没有都试,所以也不知道是否正确.... NSLocationUsageDescription 用于访问位置权限 NSCalendarsUsageDescription 用于访问日历权限 NSContactsUsageDescription 用于访问联络人 NSPhotoLibraryUsageDescription 用于访问相册 NSRemindersUsageDescription 用于访问提醒 数据 系统授权支持 位置信息 位置信息的授权状态由CLLocationManager类中的authorizationStatus方法来支持。在Info.plist文件中添加NSLocationUsageDescription关键字的描述来体现使用说明。 照片 照片的授权状态是由ALAssetsLibrary类中的authorizationStatus方法来支持。在Info.plist文件中添加关键字NSPhotoLibraryUsageDescription 的描述来体现 使用说明 音乐,视频及其它多媒体资源 由ALAssetsLibrary类的authorizationStatus方法来提供支持。 联系人 由ABAddressBookGetAuthorizationStatus函数提供支持。在Info.plist文件中添加NSContactsUsageDescription关键字的描述来体现使用说明 日历数据 由EKEventStore的authorizationStatusForEntityType:方法来提供支持。在Info.plist文件中添加NSCalendarsUsageDescription关键字的描述来体现使用说明 消息提醒 由EKEventStore的authorizationStatusForEntityType:方法来提供支持。在Info.plist文件中添加NSRemindersUsageDescription关键字的描述来体现使用说明 蓝牙设备 由CBCentralManager的state属性来提供支持。在Info.plist文件中添加NSBluetoothPeripheralUsageDescription关键字的描述来体现使用说明
一、iOS中的沙盒机制 iOS应用程序只能对自己创建的文件系统读取文件,这个独立、封闭、安全的空间,叫做沙盒。它一般存放着程序包文件(可执行文件)、图片、音频、视频、plist文件、sqlite数据库以及其他文件。 每个应用程序都有自己的独立的存储空间(沙盒) 一般来说应用程序之间是不可以互相访问 模拟器沙盒的位置 /User/userName/Library/Application Support/iPhone Simulator 当我们创建应用程序时,在每个沙盒中含有三个文件,分别是Document、Library和temp。 Document:一般需要持久的数据都放在此目录中,可以在当中添加子文件夹,iTunes备份和恢复的时候,会包括此目录。 Library:设置程序的默认设置和其他状态信息 temp:创建临时文件的目录,当iOS设备重启时,文件会被自动清除 获取沙盒目录 获取程序的根目录(home)目录 NSString *homePath = NSHomeDirectory() 获取Document目录 NSArray *paths = NSSearchPathDorDirectoriesInDomains(NSDocumentDicrectory,, NSUserDomainMark, YES); NSString *docPath = [paths lastObject]; 获取Library目录 NSArray *paths = NSSearchPathForDirectoriseInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *docPath = [paths lastObject]; 获取Library中的Cache NSArray *paths = NSSearchPathForDirectoriseInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *docPath = [paths lastObject]; 获取temp路径 NSString *temp = NSTemporaryDirectory( ); 二、NSString类路径的处理方法 文件路径的处理 NSString *path = @"/Uesrs/apple/testfile.txt" 常用方法如下 获得组成此路径的各个组成部分,结果:("/","User","apple","testfile.txt") - (NSArray *)pathComponents; 提取路径的最后一个组成部分,结果:testfile.txt - (NSString *)lastPathComponent; 删除路径的最后一个组成部分,结果:/Users/apple - (NSString *)stringByDeletingLastPathCpmponent; 将path添加到先邮路径的末尾,结果:/Users/apple/testfile.txt/app.txt - (NSString *)stringByAppendingPathConmponent:(NSString *)str; 去路径最后部分的扩展名,结果:text - (NSString *)pathExtension; 删除路径最后部分的扩展名,结果:/Users/apple/testfile - (NSString *)stringByDeletingPathExtension; 路径最后部分追加扩展名,结果:/User/apple/testfile.txt.jpg - (NSString *)stringByAppendingPathExtension:(NSString *)str; 三、NSData NSData是用来包装数据的 NSData存储的是二进制数据,屏蔽了数据之间的差异,文本、音频、图像等数据都可用NSData来存储 NSData的用法 1.NSString与NSData互相转换 NSData-> NSString NSString *aString = [[NSString alloc] initWithData:adataencoding:NSUTF8StringEncoding]; NSString->NSData NSString *aString = @"1234abcd"; NSData *aData = [aString dataUsingEncoding: NSUTF8StringEncoding]; 将data类型的数据,转成UTF8的数据 +(NSString *)dataToUTF8String:(NSData *)data { NSString *buf = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; return [buf autorelease]; } 将string转换为指定编码 +(NSString *)changeDataToEncodinString:(NSData *)data encodin:(NSStringEncoding )encodin{ NSString *buf = [[[NSString alloc] initWithData:data encoding:encodin] autorelease]; return buf; } 2. NSData 与 UIImage NSData->UIImage UIImage *aimage = [UIImage imageWithData: imageData]; //例:从本地文件沙盒中取图片并转换为NSData NSString *path = [[NSBundle mainBundle] bundlePath]; NSString *name = [NSString stringWithFormat:@"ceshi.png"]; NSString *finalPath = [path stringByAppendingPathComponent:name]; NSData *imageData = [NSData dataWithContentsOfFile: finalPath]; UIImage *aimage = [UIImage imageWithData: imageData]; 3.NSData与NSArray NSDictionary +(NSString *)getLocalFilePath:(NSString *) fileName { return [NSString stringWithFormat:@"%@/%@%@", NSHomeDirectory(),@“Documents”,fileName]; } 包括将NSData写进Documents目录 从Documents目录读取数据 在进行网络数据通信的时候,经常会遇到NSData类型的数据。在该数据是dictionary结构的情况下,系统没有提供现成的转换成NSDictionary的方法,为此可以通过Category对NSDictionary进行扩展,以支持从NSData到NSDictionary的转换。声明和实现如下: + (NSDictionary *)dictionaryWithContentsOfData:(NSData *)data { CFPropertyListRef list = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, (CFDataRef)data, kCFPropertyListImmutable, NULL); if(list == nil) return nil; if ([(id)list isKindOfClass:[NSDictionary class]]) { return [(NSDictionary *)list autorelease]; } else { CFRelease(list); return nil; } } 四、文件管理常用方法 NSFileManager 创建一个文件并写入数据 - (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary *)attr; 从一个文件中读取数据 - (NSData *)contentsAtPath:(NSString *)path; scrPath路径上的文件移动到dstPath路径上,注意这里的路径是文件路径而不是目录 - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **) error; scrPath路径上的文件复制到dstPath路径上 - (BOOL)copyItemAtPath:(NSString *)scrPath toPath:(NSString *)dstPath error:(NSError **) error; 比较两个文件的内容是否一样 - (BOOL)contentsEqualAtPath:(NSString *)path1 andPath:(NSString *)path2; 文件时候存在 - (BOOL)fileExistsAtPath:(NSString *)path; 移除文件 - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **) error; 创建文件管理 NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *path = [NSHomeDirectory( ) stringByAppendingPathComponent:@"holyBible.txt"]; NSString *text = @"abcdefg"; 将字符串转成NSData类型 NSData *data = [text dataUsingEncoding: NSUTF8StringEncoding]; 写入文件 BOOL success = [fileManager createFileAtPath:path contents:data attributes:nil]; 创建文件夹 NSString *filePath = [path stringByAppendingPathComponent:@"holyBible.txt"]; NSString *contect = @"abcdefg"; BOOL success = [fm createFileAtPath:filePath contents:[content dataUsingEncoding: NSUTF8StringEncoding] attributes:nil]; NSFileManager-读取内容 NSData *fileData = [fileManager contentsAtPath:filePath]; NSString *content = [[NSString alloc] initWithData:fileData dataUsingEncoding: NSUTF8StringEncoding]; NSData-读取内容 NSString *filePath = [path stringByAppendingPathComponent:@"holyBible.txt"]; NSData *data = [NSData dataWithContentOfFile:filePath]; NSString-读取内容 NSString *filePath = [path stringByAppendingPathComponent:@"holyBible.txt"]; NSString *content = [[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 移动、复制文件 移动文件(重命名) NSString *toPath = [NSHomeDirectory( ) stringByAppendingPathComponent:@"hellogod/New Testament.txt"]; [fm createDirectoryAtPath:[toPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; NSError *error; BOOL isSuccess = [fm moveItemAtPath:filePath toPath:toPath error:&error]; 复制文件(重命名) NSString *copyPath = [NSHomeDirectory( ) stringByAppendingPathComponent:@"备份/Old Testament.txt"]; [fm createDirectoryAtPath:[toPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; BOOL success = [fm copyItemAtPath:toPath toPath:toPath error:nil]; 删除文件、获取文件大小 判断文件是否存在和删除文件 if([fm fileExistsAtPath]) { if ([fm removeItemAtPath:copyPath]) { NSLog(@"remove success"); } } 获取文件大小 NSFileManager *fileManager = [NSFileManager defaultManager]; 获得文件的属性字典 NSDictionary *attrDic = [fileManager attributesOfItemAtpath:sourcePath error:nil]; NSNumber *fileSize = [attrDic objectForKey:NSFileSize]; 获取目录文件信息 NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *enuPath = [NSHomeDirectoty( ) stringByAppendingPathComponent:@"Test"]; NSDictionaryEnumerator *dirEnum = [fileManager enumeratorAtPath:enuPath]; NSString *path = nil; while ((path = [dirEnum nextObject]} != nil) { NSLog(@"%@",path); }
1.Ping域名、Ping某IP 有时候可能会遇到ping 某个域名或者ip通不通,再做下一步操作。这里的ping与传统的做get或者post请求还是有很大区别的。比如我们连接了某个WiFi,测试ping www.baidu.com,如果能ping 通,基本可以断定可以上网了,但是如果我们做了一个get 请求(url 是www.baidu.com),路由器可能重定向这个WiFi内的某网页了,依然没有错误返回,就会误认为可以正常上网。 这里有关于ping命令的详细解释:百度百科Ping iOS中想要ping域名或者ip,苹果提供了一个官方例子SimplePing 在例子中,有一个苹果已经封装过的类【SimplePing.h】和【SimplePing.m】 使用起来也相当的简单: 首先创建一个Ping对象: 1 2 3 4 5 SimplePing *pinger = [[SimplePing alloc] initWithHostName:self.hostName]; self.pinger = pinger; pinger.delegate = self; pinger.addressStyle = SimplePingAddressStyleICMPv4; [pinger start]; 然后在start成功的代理方法中,发送数据报文: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** * start成功,也就是准备工作做完后的回调 */ - (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address { // 发送测试报文数据 [self.pinger sendPingWithData:nil]; } - (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error { NSLog(@"didFailWithError"); [self.pinger stop]; } 其他几个代理方法也非常简单,就简单记录一下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 发送测试报文成功的回调方法 - (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { NSLog(@"#%u sent", sequenceNumber); } //发送测试报文失败的回调方法 - (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error { NSLog(@"#%u send failed: %@", sequenceNumber, error); } // 接收到ping的地址所返回的数据报文回调方法 - (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { NSLog(@"#%u received, size=%zu", sequenceNumber, packet.length); } - (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet { NSLog(@"#%s",__func__); } 注意点: iOS 中 ping失败后(即发送测试报文成功后,一直没后收到响应的报文),不会有任何回调方法告知我们。而一般ping 一次的结果也不太准确,ping 花费的时间也非常短,所以我们一般会ping多次,发送一次ping 测试报文0.5s后检测一下这一次ping是否已经收到响应。0.5s后检测时,如果已经收到响应,则可以ping 通;如果没有收到响应,则视为超时。 做法也有很多种,可以用NSTimer或者 {- (void)performSelector: withObject:afterDelay:} 这里有一个别人写的工程:https://github.com/lovesunstar/STPingTest PingTest效果图 终端ping效果图 2.获取WiFi信息 以前物联网刚火的时候,出现过很多一体式无线路由,所以App里难免会遇到要判断当前所连接的WiFi,以及获取WiFi信息的功能。13年的时候查过一些关于WiFi的方法,后面渐渐都忘记了。惭愧!!! 需要添加SystemConfiguration.framework 并在当前类中添加代码 #import arpa/inet.h>#import netinet/in.h>#import ifaddrs.h> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //获取WiFi 信息,返回的字典中包含了WiFi的名称、路由器的Mac地址、还有一个Data(转换成字符串打印出来是wifi名称) - (NSDictionary *)fetchSSIDInfo { NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces(); if (!ifs) { return nil; } NSDictionary *info = nil; for (NSString *ifnam in ifs) { info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam); if (info && [info count]) { break; } } return info; } //打印出来的结果: 2016-05-12 15:28:51.674 SimplePing[18883:6790207] WIFI_INFO:{ BSSID = "a4:2b:8c:c:7f:bd"; SSID = bdmy06; SSIDDATA = ; } 3.获取WiFi名称 有了上一步,获取WiFi名称就非常简单了。 1 2 3 NSString *WiFiName = info[@"SSID"]; //打印结果: 2016-05-12 15:35:13.059 SimplePing[18887:6791418] bdmy06 完整的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (NSString *)fetchWiFiName { NSArray *ifs = (__bridge_transfer NSArray *)CNCopySupportedInterfaces(); if (!ifs) { return nil; } NSString *WiFiName = nil; for (NSString *ifnam in ifs) { NSDictionary *info = (__bridge_transfer NSDictionary *)CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam); if (info && [info count]) { // 这里其实对应的有三个key:kCNNetworkInfoKeySSID、kCNNetworkInfoKeyBSSID、kCNNetworkInfoKeySSIDData, // 不过它们都是CFStringRef类型的 WiFiName = [info objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID]; // WiFiName = [info objectForKey:@"SSID"]; break; } } return WiFiName; } 4.获取当前所连接WiFi的网关地址 例如自己家的路由器一般默认的网关地址是192.168.1.1,获取的就是这个192.168.1.1。 为什么不直接写死呢? 因为一些商场或者有多个路由器的网关地址是不一样的,比如之前有个公司的网关是192.168.89.1。 这里有篇博客,这是地址 需要导入的库: 1 #import #import #import 获取网关的方法 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 45 46 - (NSString *)getGatewayIpForCurrentWiFi { NSString *address = @"error"; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; //*/ while(temp_addr != NULL) { /*/ int i=255; while((i--)>0) //*/ if(temp_addr->ifa_addr->sa_family == AF_INET) { // Check if interface is en0 which is the wifi connection on the iPhone if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { // Get NSString from C String //ifa_addr //ifa->ifa_dstaddr is the broadcast address, which explains the "255's" // address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr)]; address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; //routerIP----192.168.1.255 广播地址 NSLog(@"broadcast address--%@",[NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr)]); //--192.168.1.106 本机地址 NSLog(@"local device ip--%@",[NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]); //--255.255.255.0 子网掩码地址 NSLog(@"netmask--%@",[NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr)]); //--en0 端口地址 NSLog(@"interface--%@",[NSString stringWithUTF8String:temp_addr->ifa_name]); } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); in_addr_t i = inet_addr([address cStringUsingEncoding:NSUTF8StringEncoding]); in_addr_t* x = &i; unsigned char *s = getdefaultgateway(x); NSString *ip=[NSString stringWithFormat:@"%d.%d.%d.%d",s[0],s[1],s[2],s[3]]; free(s); return ip; } 其中 getdefaultgateway 是一个C语言文件中的方法,在工程里可以找到。 5.获取本机在WiFi环境下的IP地址 获取本机在WiFi环境下的ip地址,在上一节中其实已经写过,这里将其提取出来即可: 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 - (NSString *)getLocalIPAddressForCurrentWiFi { NSString *address = nil; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; while(temp_addr != NULL) { if(temp_addr->ifa_addr->sa_family == AF_INET) { // Check if interface is en0 which is the wifi connection on the iPhone if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; return address; } } temp_addr = temp_addr->ifa_next; } freeifaddrs(interfaces); } return nil; } 同样的方式也可以获取广播地址、子网掩码、端口等,组装成一个字典。 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 45 46 47 48 49 - (NSMutableDictionary *)getLocalInfoForCurrentWiFi { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; //*/ while(temp_addr != NULL) { if(temp_addr->ifa_addr->sa_family == AF_INET) { // Check if interface is en0 which is the wifi connection on the iPhone if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { //----192.168.1.255 广播地址 NSString *broadcast = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr)]; if (broadcast) { [dict setObject:broadcast forKey:@"broadcast"]; } NSLog(@"broadcast address--%@",broadcast); //--192.168.1.106 本机地址 NSString *localIp = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; if (localIp) { [dict setObject:localIp forKey:@"localIp"]; } NSLog(@"local device ip--%@",localIp); //--255.255.255.0 子网掩码地址 NSString *netmask = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr)]; if (netmask) { [dict setObject:netmask forKey:@"netmask"]; } NSLog(@"netmask--%@",netmask); //--en0 端口地址 NSString *interface = [NSString stringWithUTF8String:temp_addr->ifa_name]; if (interface) { [dict setObject:interface forKey:@"interface"]; } NSLog(@"interface--%@",interface); return dict; } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); return dict; } 将返回的字典打印出来: 1 2 3 4 5 6 2016-05-12 17:59:09.257 SimplePing[19141:6830567] wifi:{ broadcast = "192.168.1.255"; interface = en0; localIp = "192.168.1.7"; netmask = "255.255.255.0"; }