开始之前,祝大家新年快乐,身体健康,万事如意,也祝各位单身狗早日脱单(虽然我也是单身狗),相信过年都还坐在电脑前的不是单身狗就是技术狂。
好了,废话也不多说,come on….
iOS 开发中 Objective-C 和 Swift 都用的是 Clang / LLVM 来编译的。LLVM是一个模块化和可重用的编译器和工具链技术的集合,Clang 是 LLVM 的子项目,是 C,C++ 和 Objective-C 编译器,目的是提供惊人的快速编译,比 GCC 快3倍,其中的 clang static analyzer 主要是进行语法分析,语义分析和生成中间代码,当然这个过程会对代码进行检查,出错的和需要警告的会标注出来。LLVM 核心库提供一个优化器,对流行的 CPU 做代码生成支持。lld 是 Clang / LLVM 的内置链接器,clang 必须调用链接器来产生可执行文件。
LLVM 比较有特色的一点是它能提供一种代码编写良好的中间表示 IR,这意味着它可以作为多种语言的后端,这样就能够提供语言无关的优化同时还能够方便的针对多种 CPU 的代码生成。
LLVM是编译器和工具链技的集合,Clang才是真正的编译器,Clang必须调用链接器(内置lldb)来产生可执行文件。
摘自https://linuxtoy.org/archives/llvm-and-clang.html
LLVM(Low Level Virtual Machine):编译器的后台——进行程序语言的编译期优化、链接优化、在线编译优化、代码生成(优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time))
Clang::编译器(LLVM)的前端— 是一个 C++ 编写、基于 LLVM、发布于 LLVM BSD 许可证下的 C/C++/Objective C/Objective C++ 编译器,其目标(之一)就是超越 GCC
LLVM 还被用在 Gallium3D 中进行 JIT 优化,Xorg 中的 pixman 也有考虑使用 LLVM 来优化执行速度,llvm-lua 使用 LLVM 来编译 Lua 代码,gpuocelot 使用 LLVM 可以令 CUDA 程序无需重新编译即可运行在多核 X86CPU、IBM Cell、支持 OpenCL 的设备之上...
LLVM,做语法树分析,实现语言转换、字符串加密。编写ClangPlugin,扩展功能。编写Pass,代码混淆优化。
编译速度快:在某些平台上,Clang 的编译速度显著的快过 GCC。
占用内存小:Clang 生成的 AST 所占用的内存是 GCC 的五分之一左右。
模块化设计:Clang 采用基于库的模块化设计,易于 IDE 集成及其他用途的重用。
诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告。
支持更多语言:GCC 除了支持 C/C++/Objective-C, 还支持 Fortran/Pascal/Java/Ada/Go 和其他语言。Clang 目前支持的语言有 C/C++/Objective-C/Objective-C++。
加强对 C++ 的支持:Clang 对 C++ 的支持依然落后于 GCC,Clang 还需要加强对 C++ 提供全方位支持。
支持更多平台:GCC 流行的时间比较长,已经被广泛使用,对各种平台的支持也很完备。Clang 目前支持的平台有 Linux/Windows/Mac OS。
GCC 原名为GNU C语言编译器
支持 JAVA/ADA/FORTRAN
当前的 Clang 的 C++ 支持落后于 GCC,参见。(近日 Clang 已经可以自编译,见)
GCC 支持更多平台
GCC 更流行,广泛使用,支持完备
GCC 基于 C,不需要 C++ 编译器即可编译
clang驱动:利用现有OS、编译环境以及参数选项来驱动整个编译过程的工具。
clang编译器:利用clang前端组件及库打造的编译器,其入口为cc1_main; 参数为clang -cc1 或者 -Xclang;
clang前端组件及库:包括Support、Basic、Diagnostics、Preprocessor、Lexer、Sema、CodeGen等;
编译器前端负责产生机器无关的中间代码
编译器后端负责对中间代码进行优化并转化为目标机器代码,
预处理(Pre-process):把宏替换,删除注释,展开头文件,产生 .i 文件。
编译(Compliling):把之前的 .i 文件转换成汇编语言,产生 .s文件。
汇编(Asembly):把汇编语言文件转换为机器码文件,产生 .o 文件。
链接(Link):对.o文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。
编译信息写入辅助文件,创建文件架构 .app 文件
处理文件打包信息
执行 CocoaPod 编译前脚本,checkPods Manifest.lock
编译.m文件,使用 CompileC 和 clang 命令
链接需要的 Framework
编译 xib
拷贝 xib ,资源文件
编译 ImageAssets
处理 info.plist
执行 CocoaPod 脚本
拷贝标准库
创建 .app 文件和签名
预处理 -> 词法分析 (token ) ->语法分析 ->语义分析 ->中间代码生成 -> 生成字节码-> 优化 -> 生成汇编代码 -> Link -> 目标文件 ->生层可执行文件。
预处理:处理源文件中#开头的预编译命令,宏的替换
词法分析 (token ):把代码切成一个个Token(词法/代码符号),大小括号,等于号,字符串
语法分析:符号化的字符串,转化抽象为可以被计算机存储的树形结构,即抽象语法树(AST),检测正确性
语义分析:语义分析器就会发现类型不匹配,编译器提出相应的错误警告。主要是类型检查、以及符号表管理
中间代码生成:编译器前端负责产生机器无关的中间代码,编译器后端负责对中间代码进行优化并转化为目标机器代码
生成字节码/目标代码:编译器后端主要包括代码生成器、代码优化器。代码生成器将中间代码转换为目标代码,代码优化器主要是进行一些优化,比如删除多余指令,选择合适寻址方式等
目标代码需要经过汇编器处理,才能变成机器上可以执行的指令。生成对应的.o文件
链接器(这里指的是静态链接器)将多个目标文件合并为一个可执行文件,在 OS X 和 iOS中的可执行文件是 Mach-O,对于Mach-O的文件格式可以参考这里,刚才所描述的过程其实可以用 sunnyxx的一页 ppt来进行总结
如果多个程序都用到了一个库,那么每个程序都要将其链接到可执行文件中,非常冗余,动态链接的话,多个程序可以共享同一段代码,不需要在磁盘上存多份拷贝,但是动态链接发生在启动或运行时,增加了启动时间,造成一些性能的影响。
静态库不方便升级,必须重新编译,动态库的升级更加方便
.app目录中,有又一个叫_CodeSignature的子目录,这是一个 plist文件,里面包含了程序的代码签名,你的程序一旦签名,就没有办法更改其中的任何东西,包括资源文件,可执行文件等,iOS系统会检查这个签名。
签名过程本身是由命令行工具 codesign 来完成的。如果你在 Xcode中build一个应用,这个应用构建完成之后会自动调用codesign 命令进行签名,这也是Link之后的一个关键步骤。
启动过程中,dyld(动态链接器) 起了很重要的作用,进行动态链接,进行符号和地址的一个绑定
加载所依赖的dylibs
Fix-ups:Rebase修正地址偏移,因为 OS X和 iOS 搞了一个叫 ASLR的东西来做地址偏移(随机化)来避免收到攻击
Fix-ups:Binding确定 Non-Lazy Pointer地址,进行符号地址绑定。
ObjC runtime初始化:加载所有类
Initializers:执行load 方法和__attribute__((constructor))修饰的函数
宏观的LLVM,指的是整个的LLVM的框架,它肯定包含了Clang,因为Clang是LLVM的框架的一部分,是它的一个C/C++的前端。虽然这个前端占的比重比较大,但是它依然只是个前端,LLVM框架可以有很多个前端和很多个后端,只要你想继续扩展。
微观的LLVM指的是以实际开发过程中,包括实际使用过程中,划分出来的LLVM。比如编译LLVM和Clang的时候,LLVM的源码包是不包含Clang的源码包的,需要单独下载Clang的源码包。
所以这里想讨论的就是微观的LLVM和Clang的关系。从编译器用户的角度,Clang使用了LLVM中的一些功能,目前所知道的主要就是对中间格式代码的优化,或许还有一部分生成代码的功能。从Clang和微观LLVM的源码位置可以看出,Clang是基于微观的LLVM的一个工具。而从功能的角度来说,微观的LLVM可以认为是一个编译器的后端,而Clang是一个编译器的前端,它们的关系就更加的明了了,一个编译器前端想要程序最终变成可执行文件,是缺少不了对编译器后端的介绍的。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
Node.js什么时候出现,2009年,Ryan Dahl(瑞恩·达尔)在GitHub上发布了最初版本的部分Node.js包,随后几个月里,有人开始使用Node.js开发应用
什么是Node.js,做过Javascript开发的,看到Node.js这个名字,初学者可能会误以为这是一个Javascript应用,事实上,Node.js采用C++语言编写而成,是一个Javascript的运行环境,意思就是底层使用c++编写,外层封装采用Javascript,需要使用Javascript解析执行。
比如OC底层也是c++,但是执行代码,只需要解析OC代码。
Node.js是一个后端的Javascript运行环境,这意味着你可以编写服务器端的Javascript代码,交给Node.js来解释执行。
传统Web服务器原理(T):传统的网络服务技术,是每个新增一个连接(请求)便生成一个新的线程,这个新的线程会占用系统内存,最终会占掉所有的可用内存。
Node.js工作原理(T):只运行在一个单线程中,使用非阻塞的异步 I/O 调用,所有连接都由该线程处理,也就是一个新的连接,不会开启新的线程,仅仅一个线程去处理多个请求。
优缺点:
传统的比较消耗内存,Node.js只开启一个线程,大大减少内存消耗。
假设是普通的Web程序,新接入一个连接会占用 2M 的内存,在有 8GB RAM的系统上运行时, 算上线程之间上下文切换的成本,并发连接的最大理论值则为 4000 个。这是在传统 Web服务端技术下的处理情况。而 Node.js 则达到了约 1M 一个并发连接的拓展级别
Node.js弊端:大量的计算可能会使得 Node 的单线程暂时失去反应, 并导致所有的其他客户端的请求一直阻塞, 直到计算结束才恢复正常
疑问?Node.js是单线程的。单线程怎么开启异步?怎么工作的? 需要了解事件驱动。
什么是事件驱动?
传统的web server多为基于线程模型。你启动Apache或者什么server,它开始等待接受连接。当收到一个连接,server保持连接连通直到页面或者什么事务请求完成。如果他需要花几微妙时间去读取磁盘或者访问数据库,web server就阻塞了IO操作(这也被称之为阻塞式IO).想提高这样的web server的性能就只有启动更多的server实例。
Node.Js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)
本质:当然最终处理事件还是需要底层开启线程,只不过接受请求只用一个线程去接收。
Node.js使用Module模块去划分不同的功能,以简化App开发,Module就是库,跟组件化差不多,一个功能一个库。
NodeJS内建了一个HTTP服务器,可以轻而易举的实现一个网站和服务器的组合,不像PHP那样,在使用PHP的时候,必须先搭建一个Apache之类的HTTP服务器,然后通过HTTP服务器的模块加载CGI调用,才能将PHP脚本的执行结果呈现给用户
require() 函数,用于在当前模块中加载和使用其他模块;
Express是Node.JS第三方库
Express可以处理各种HTTP请求
Express是目前最流行的基于Node.js的Web开发框架,
Express框架建立在node.js内置的http模块上,可以快速地搭建一个Web服务器
打开终端,输入node -v,先查看是否已经安装
如果没有安装,就需要安装node软件。
mac上可以使用Homebrew,安装node
Homebrew:Homebrew简称brew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件,相当于window上360管家,可以帮你下载软件。
先输入brew -v,查看mac是否安装了HomeBrew
安装ruby教程(http://www.jianshu.com/p/daa92187621c)
使用ruby安装Homebrew,前提是安装了ruby
输入指令安装brew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
使用Homebrew安装Node,输入指令
brew install node
安装完,输入`node -v``查看是否安装成功
NPM是随同NodeJS一起安装的包管理工具,用于下载NodeJS第三方库。
类似iOS开发中cocoapods,用于安装第三方框架
新版的NodeJS已经集成了npm,所以只要安装好Node.JS就好
package.json
package.json类似cocoapods中的Podfile文件
package.json文件描述了下载哪些第三方框架.
可以使用npm init创建
需要添加dependencies字段,描述添加哪些框架,其他字段随便填
注意:不能有中文符号
"dependencies": {
"express": "^4.14.0",
"socket.io": "^1.4.8"
}
npm install
或者你如果不喜欢使用命令的话这个方案相信是最好的选择。
访问nodejs官网,点击蓝色选框区域稳定版,并下载https://nodejs.org/en/
双击刚下载的文件,按步骤默认安装就行
安装完成后打开终端,输入
npm -v
node -v
两个命令,如下图出现版本信息,说明安装成功。
使用上面任何一种种方式完成以后,就可以开始测试了。
新建一个js文件,nodejsTest.js , 输入下面的代码, 并保存
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {
"Content-Type" : "text/plain"
});
response.write("Welcome to Nodejs");
response.end();
}).listen(8000, "127.0.0.1");
console.log("Creat server on http://127.0.0.1:8000/");
打开终端进入 nodejsTest.js 所在目录, 输入 node nodejsTest
打开浏览器,点击或者输入http://127.0.0.1:8000/%EF%BC%8C 如果无法打开,可以去掉.listen(8000, “127.0.0.1”) 中得ip监听改成 .listen(8000),然后点击或者输入http://localhost:8000/
关于Node.js其他的相关配置可以查看http://www.jianshu.com/p/d76ecf5ed690,笔者也就是好奇所以是了一下,希望不会被喷。
后面有机会会结合客户端实现数据的交互相关处理。
参考: http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.csdn.net/article/a/2016-07-12/3358
http://www.yiibai.com/nodejs/nodejs-quick-start.html
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
上线了这么多App,关于App上线的坑,也遇到了很多,但是这一次是最严重,也是最值得思考的,所以我打算翻云覆雨一番。
构建版本中找不到提交的二进制文件
可能有人会说,这个一般等个20分钟就有了的,如果是这么简单,那我还在这里写个鬼啊!
所以,等待被咔嚓了,因为我等了一个晚上。。。。。
因为年前的最后第二个版本就在昨晚打包提交(不要问我为什么是最后第二个,因为放假的那天晚上还有一场奋斗),因为我一般的习惯是公司打包提交之后,回到家里再点提交以供审核,所以就直接回家了。
成功上传提示
回到家里撒野没管,该干撒干撒,看书,看电影,和家里视频。。。。10点钟的时候,被老大告知,itnues connect里面是空的,不应该,从没遇到过这种问题,我在想估计又是苹果的坑,网上一搜,原来还真有一大把类似问题发,其中大部分这样的是因为我或者UI不细心。
好了下面细数一下整个过程。
运行成功,打包也成功,但是打包的时候报了一个:pngcrush caught libpng error:类似错误(这里即时有错误还是成功打包)
1. 在build settings里把工程里的Compress PNG files设置为NO,问题解决,但这样设置以后,弄出来的ipa会很大,感觉不是很理想。
2. mac上的preview(预览)打开出问题的png文件,然后重新导出为png文件或者用photoshop把png图片保存为NOT INTERLACED(不交错)的,这样真机调试时就没有错误了。
While reading /Volumes/data2/project/ChildStory/ChildStory/nav_bar.png pngcrush caught libpng error:
Could not find file: /Users/hopo/Library/Developer/Xcode/DerivedData/ChildStory-cwdwhztnszhpawbnlproivndbuvw/Build/Products/Debug-iphoneos/ChildStory.app/nav_bar.png
Command /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/copypng emitted errors but did not return a nonzero exit code to indicate failure
该文件不是真正的png文件,可能是个jpg文件(被你手动更改的),实际的文件头信息是不一样的,造成不能识别。
解决方法有两种:
1. 重新把图片文件处理成png文件
2. 修改文件名后缀,比如改成.jpg
提交打包提交App,将包上传到iTunes Connect之后,以为就能发布了,便点击构建版本,发现没有刚刚上传的包,于是就点击"预发行"看一下,会看到"已上传",过不久再刷新一次再看,就变成了二进制无效,无比的郁闷,上传了五六次都是二进制文件无效,
自2015年2月份开始,新上传到iTunes上面审核的app,必须支持64位,新上传是指第一次上传,
或者没有审核通过过,总之就是在AppStore上面没有上架的app,必须支持64位,包括工程里面的代码和用到的静态库文件
1. build setting中Archivecture指定arm64
2. Schemes的Analyze和Archive设置release模式
3. Build Active Architecture Only:是否只编译当前设备适用的指令集(如果这个参数设为YES,使用iPhone 6调试,那么最终生成的一个支持ARM64指令集的Binary。一般在DEBUG模式下设为YES,RELEASE设为NO)。
Valid architectures:即将编译的指令集。(Valid architectures 和 Architecture两个集合的交集为最终编译生成的版本)
Architectures:你想支持的指令集。(支持指令集是通过编译生成对应的二进制数据包实现的,如果支持的指令集数目有多个,就会编译出包含多个指令集代码的数据包,造成最终编译的包很大。)
温馨提示+警钟
上线的时候一定要切忌(作为一个好的程序员应有的习惯)
1. 不要TMD直接修改jpg<-->png。
2. 一定要跑真机,一定要Analyze,一定要跑一下Profile里面常见的工具。
3. 编译,运行,打包的时候一定不能放过任何错误,一个都不行。
4. 编译,运行,打包(尤其是打包)的时候一定要尽量减少警告。
5. archive之前一定要细心检查相关配置,release/debug,API切换,脚本配置等等
6. Archive-Upload之后,最好等提交以供审核了之后再下班,或者带电脑回去,不然运气不好加班也白加了。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
因为我一直都是比较喜欢学习的,尤其是研究技术,关于非技术的的实在太多,而且很多需要自己亲身感受才能体会的。所以这里就先总结一下去年一年个人在技术上的发展。然后对接下来的一年和和以后的规划。
项目: 真正从零开始架构一个项目,并不像之前做外包那样瞎整一条。这其中考虑到了,设计模式,架构模式,多线程处理,音视频处理,性能处理,数据持久性优化等,都有综合考虑,虽然还有欠缺的地方,但是总之跟之前做的东西里面还是有一个很大的突破的。
直播:今年可谓是个直播元年,可以看到的是,各大公司和部分创业型公司都开始往直播的方向发展,很多女性,不管长的美不美,或者有没有才华,年不年轻,都有试着或者想做一下直播,有的是专业,有的是业余,或许为了娱乐,或许为了生活,甚至很多男性或者屌丝男都开始玩起直播了。
前后端:这一年和往年不同的另一个热点就是,关于iOS行业,记得12年学习iOS的时候,几乎发现不到多少的人在学习iOS,或者苹果相关开发,做这个的也少之又少。基本上如果你会拖个界面或者写一些简单的逻辑都可以找到一份还可以的工作。但是,由于培训机构的大量兴起,各大高校大学毕业生的涌入,导致从2016年初开始市面上就开始流行一个词:iOS烂大街(自己体会)。
所以在这样的背景下,我不得不在深入学习iOS的同时,在业余的时间或者抽出更多的时间学学习一些其他的技术,不管是因为趋势还是为了以后留一条后路,对于一个程序员来时,学习是无时无刻的。
因为之前有过一点H5的经验,而且去年下半年小程序的火热,我选择的深入研究一下JavaScript(没有研究CSS,Html5),网上买了几本高级的书有空就看看练练,还是蛮有收获的。
另一门技术就是php,我之所以选择学习php,有两个原因,在这个行业也有一段时间了,也不可能一辈子这样干下去,就算不创业,也要考虑或者向管理层发展,而几乎所有公司都不会让一个做iOS的带领整个技术部,至少我现在没有发现。还有一个原因是,后台目前用的最多的也就是java和php,因为大部分大学有学习java这本们语言,所以就是在大学学的不好,哪怕偶尔看一下书,也是比PHP有优势的,php基本上没有学校对这门课进行开设课程,唯一有的也就是一些爱好者,或者培训出来的人,所以导致php目前在市面上还是很紧缺的,网上不是传说一句话:php是世界上最好的语言。具体是不是我也不知道,只有深入学习过了才能体会这句话的真谛。
最后一个就是大部分程序员的通病,算法,数据结构,设计模式,这里就要感谢我。。。在开发的过程中,看到了我的代码,然后做了一下评价,并且给了一些很受益的建议,所以我也专门抽空学习了一些重要的算法,和大学欠下的债,现在也有空就会看看相关的东西或者尽量应用到实战,希望能提高自己在这一块的思维。
接下来的一年除了将 自己本分工作做好的同时,会专门而且不断的深入学习与实战PHP技术,希望能做到一个真正的转型,即时我暂时还不会靠这个吃饭。
我喜欢用笔记记录下这一过程,大概的内容有
希望能一步一步学好这门技术。至于未来,我曾说过每年都会学习并且入门一门语言,这个虽然有点夸张,不现实,而且没有必要,但是我总归去试试,我相信。。。。。。
关于以后的路,或者做管理,或者创业,拿都是以后的事情,谁也说不准,就看我有没有这种心并且为之付出应有的努力!
最后我也会抽空出来锻炼身体:身体才是革命的本钱,身体垮了学再多东西,转太多钱都没用 还会偶尔出去旅游一下,既能散心,又能看看风景,最主要的是还能看看外面的世界,提高一下自己眼界与对待人,事与对待生活的方式 最后争取今年提辆车,虽然有点困难,有点不现实,嘿嘿!
至于其他的就太多的,谁也不知会发生什么,得自己慢慢经历了。
不管怎么样,要用微笑面对生活,善待身边的每一个人,学会感恩,不忘初心的付出,我相信总有一天你能得到不一样的收获,即时没有什么成功,但是当你在回过头来看看这一切的时候,依然是很值得回忆,并且是一笔无价的财富。
。 。。 。。。 。。。。 。。。。。 。。。。。。 。。。。。。。 。。。。。。。。 。。。。。。。。。 。。。。。。。。。。 。。。。。。。。。。。 。。。。。。。。。。。。 。。。。。。。。。。。。。 。。。。。。。。。。。。。。 保持初心,继续前进。。。。。。 。。。。。。。。。。。。。。 。。。。。。。。。。。。。 。。。。。。。。。。。。 。。。。。。。。。。。 。。。。。。。。。。 。。。。。。。。。 。。。。。。。。 。。。。。。。 。。。。。。 。。。。。 。。。。 。。。 。。 。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
#!/bin/sh
#LEPgyerApiKey 在Info.plist中配置蒲公英apiKey
#LEPgyerUKey 在Info.plist中配置蒲公英ukey
result=''
uploadToPgyer()
{
echo "params"
echo "ipa路径: " $1
echo "UserKey: " $2
echo "ApiKey: " $3
echo "Password:" $4
result=$(curl -F "file=@$1" -F "uKey=$2" -F "_api_key=$3" -F "publishRange=2" -F "isPublishToPublic=2" -F "password=$4" 'https://www.pgyer.com/apiv1/app/upload' | json-query data.appShortcutUrl)
}
tempPath="$(pwd)"
if [ ! -f pkgtopgy_path.config ] ; then
touch pkgtopgy_path.config
fi
lines=`sed -n '$=' pkgtopgy_path.config`
if [[ $lines == '' ]]; then
lines=0
fi
echo "请选择你需要打包的目录:"
for i in `cat pkgtopgy_path.config `
do
echo $((++no)) ":" $i
done
echo $((++no)) ":" "${tempPath}"
echo "若没有符合需求的路径,请直接回车"
read -p "你的选择是:" pathselection
if [[ $pathselection >0 ]] && [[ $pathselection -le `expr $lines+1` ]] ; then
if [[ $pathselection -le $lines ]] ; then
project_path=`sed -n ${pathselection}p pkgtopgy_path.config`
else
echo "已选目录:${tempPath}"
read -p "请确认上述已选目录:(y/n)" checkPath
if [[ $checkPath = "y" ]] ; then
project_path=$tempPath
fi
fi
else
echo "未找到合适的路径"
fi
if [[ $project_path == '' ]]; then
read -p "请手动输入打包工程的绝对路径:" inputPath
project_path=$inputPath
if [[ $project_path != '' ]]; then
echo $project_path >> pkgtopgy_path.config
cat pkgtopgy_path.config
fi
fi
if [[ -d "$project_path" ]]; then
echo "当前路径为:" $project_path
else
echo "路径:"$project_path
echo "当前路径有误,已终止!!!\n"
exit
fi
SECONDS=0
#取当前时间字符串添加到文件结尾
now=$(date +"%Y_%m_%d_%H_%M_%S")
#工程名
cd ${project_path}
project=$(ls | grep xcodeproj | awk -F.xcodeproj '{print $1}')
#指定项目地址
workspace_path="$project_path/${project}.xcworkspace"
if [[ ! -d "$workspace_path" ]]; then
echo "路径:"$workspace_path
echo "未找到.xcworkspace文件,已终止!!!"
exit
fi
#工程配置文件路径
echo "检查蒲公英设置"
project_infoplist_path=${project_path}/${project}/Info.plist
pgyerApiKey=''
pgyerUKey=''
pgyerApiKey=$(/usr/libexec/PlistBuddy -c "print LEPgyerApiKey" ${project_infoplist_path})
pgyerUKey=$(/usr/libexec/PlistBuddy -c "print LEPgyerUKey" ${project_infoplist_path})
pgyPassword=$(/usr/libexec/PlistBuddy -c "print LEPgyerPassword" ${project_infoplist_path})
if [[ $pgyerUKey = '' ]] || [[ $pgyerApiKey = '' ]]; then
read -p "发现尚未配置蒲公英上传的apiKey及ukey,是否配置?(y/n)" checkConfig
if [[ $checkConfig = "y" ]] ; then
read -p "请输入蒲公英上传的apiKey:" apikey
pgyerApiKey=$apikey
read -p "请输入蒲公英上传的ukey:" ukey
pgyerUKey=$ukey
else
if [[ $pgyPassword = '' ]]; then
echo '发现蒲公英下载密码,未在工程项目的Info.plist配置,配置名称为LEPgyerPassword'
fi
read -p "是否继续打包?(y/n)" checkPkg
if [[ $checkPkg = "n" ]] ; then
exit
fi
fi
fi
#指定项目的scheme名称
scheme=$project
#指定要打包的配置名
configuration="Release"
#指定打包所使用的输出方式,目前支持app-store, package, ad-hoc, enterprise, development, 和developer-id,即xcodebuild的method参数
export_method='development'
#export_method='app-store'
#指定输出路径
mkdir "${HOME}/Desktop/${project}_${now}"
output_path="${HOME}/Desktop/${project}_${now}"
echo $output_path
#指定输出归档文件地址
archive_path="$output_path/${project}_${now}.xcarchive"
#指定输出ipa地址
ipa_path="$output_path/${project}_${now}.ipa"
#指定输出ipa名称
ipa_name="${project}_${now}.ipa"
#获取执行命令时的commit message
commit_msg="$1"
#输出设定的变量值
echo "=================AutoPackageBuilder==============="
echo "begin package at ${now}"
echo "workspace path: ${workspace_path}"
echo "archive path: ${archive_path}"
echo "ipa path: ${ipa_path}"
echo "export method: ${export_method}"
echo "commit msg: $1"
#pod update
#pod update --no-repo-update
#先清空前一次build
#gym --workspace ${workspace_path} --scheme ${scheme} --clean --configuration ${configuration} --archive_path ${archive_path} --export_method ${export_method} --output_directory ${output_path} --output_name ${ipa_name}
gym --workspace ${workspace_path} --scheme ${scheme} --clean --configuration ${configuration} --export_method ${export_method} --output_directory ${output_path} --output_name ${ipa_name}
#输出总用时
echo "==================>Finished. Total time: ${SECONDS}s"
if [[ $pgyerUKey = '' ]] || [[ $pgyerApiKey = '' ]]; then
echo "未在工程项目的Info.plist文件中配置LEPgyerApiKey(蒲公英apiKey)及LEPgyerUKey(蒲公英userKey),因此无法上传项目至蒲公英平台"
else
if [[ -f "$ipa_path" ]]; then
uploadToPgyer $ipa_path $pgyerUKey $pgyerApiKey $pgyPassword
while [[ $result == '' ]]
do
read -p "上传失败,是否重新上传到蒲公英?(y/n)" reUploadToPgyer
if [[ $reUploadToPgyer = "y" ]] ; then
uploadToPgyer $ipa_path $pgyerUKey $pgyerApiKey $pgyPassword
else
echo "本次打包完成,ipa位置: ${ipa_path}"
exit
fi
done
if [[ $result != '' ]]; then
echo "请前往此处下载最新的app" http://www.pgyer.com/$result
open http://www.pgyer.com/$result
fi
fi
fi
echo "本次打包完成"
exit
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
数据库就是用来存储和管理数据的仓库!
数据库存储数据的优先:
可存储大量数据;
方便检索;
保持数据的一致性、完整性;
安全,可共享;
通过组合分析,可产生新数据。
没有数据库,使用磁盘文件存储数据;
层次结构模型数据库;
网状结构模型数据库;
关系结构模型数据库:
使用二维表格来存储数据;
关系-对象模型数据库;
MySQL就是关系型数据库!
Oracle(神喻):甲骨文(最高!);
DB2:IBM;
SQ Server:微软;
Sybase:赛尔斯;
MySQL:甲骨文;
RDBMS = 管理员(manager)+仓库(database)
database = N个table
table:
表结构:定义表的列名和列类型!
表记录:一行一行的记录!
我们现在所说的数据库泛指“关系型数据库管理系统(RDBMS - Relationa database management system)”,即“数据库服务器”。
当我们安装了数据库服务器后,就可以在数据库服务器中创建数据库,每个数据库中还可以包含多张表。
表头(header): 每一列的名称;
列(row): 具有相同数据类型的数据的集合;
行(col): 每一行用来描述某个人/物的具体信息;
值(value): 行的具体信息, 每个值必须与该列的数据类型相同;
键(key): 表中用来识别某个特定的人\物的方法, 键的值在当前列中具有唯一性。
数据库表就是一个多行多列的表格。在创建表时,需要指定表的列数,以及列名称,列类型等信息。而不用指定表格的行数,行数是没有上限的
应用程序使用数据库完成对数据的存储!
启动:net start mysql;
关闭:net stop mysql;
在启动MySQL服务器后,我们需要使用管理员用户登录MySQL服务器,然后来对服务器进行操作。
-u:后面的root是用户名,这里使用的是超级管理员root;
-p:后面的123是密码,这是在安装MySQL时就已经指定的密码;
-h:后面给出的localhost是服务器主机名,它是可以省略的,例如:mysq -u root -p 123;
SQL(Structured Query Language)是“结构化查询语言”,它是对关系型数据库的操作语言。它可以应用到所有关系型数据库中,例如:MySQL、Oracle、SQ Server等。SQ标准(ANSI/ISO)有:
SQL-92:1992年发布的SQL语言标准;
SQL:1999:1999年发布的SQL语言标签;
SQL:2003:2003年发布的SQL语言标签;
这些标准就与JDK的版本一样,在新的版本中总要有一些语法的变化。不同时期的数据库对不同标准做了实现。
虽然SQL可以用在所有关系型数据库中,但很多数据库还都有标准之后的一些语法,我们可以称之为“方言”。例如MySQL中的LIMIT语句就是MySQL独有的方言,其它数据库都不支持!当然,Oracle或SQ Server都有自己的方言。
SQL语句可以单行或多行书写,以分号结尾;
可以用空格和缩进来来增强语句的可读性;
关键字不区别大小写,建议使用大写;
DDL(Data Definition Language):数据定义语言,用来定义数据库对象:库、表、列等;
DML(Data Manipulation Language):数据操作语言,用来定义数据库记录(数据);
DCL(Data Contro Language):数据控制语言,用来定义访问权限和安全级别;
DQL(Data Query Language):数据查询语言,用来查询记录(数据)。
查看所有数据库名称:
SHOW DATABASES;
切换数据库:
USE mydb1,切换到mydb1数据库;
创建数据库:
CREATE DATABASE [IF NOT EXISTS] mydb1;
创建数据库,例如:
CREATE DATABASE mydb1,创建一个名为mydb1的数据库。如果这个数据已经存在,那么会报错。例如CREATE DATABASE IF NOT EXISTS mydb1,在名为mydb1的数据库不存在时创建该库,这样可以避免报错。
删除数据库:
DROP DATABASE [IF EXISTS] mydb1;
删除数据库,例如:
DROP DATABASE mydb1,删除名为mydb1的数据库。如果这个数据库不存在,那么会报错。DROP DATABASE IF EXISTS mydb1,就算mydb1不存在,也不会的报错。
修改数据库编码:
ALTER DATABASE mydb1 CHARACTER SET utf8
修改数据库mydb1的编码为utf8。注意,在MySQL中所有的UTF-8编码都不能使用中间的“-”,即UTF-8要书写为UTF8。
MySQL有三大类数据类型, 分别为数字、日期\时间、字符串, 这三大类中又更细致的划分了许多子类型:
整数: tinyint、smallint、mediumint、int、bigint
浮点数: float、double、real、decimal
date、time、datetime、timestamp、year
字符串: char、varchar
文本: tinytext、text、mediumtext、longtext
二进制(可用来存储图片、音乐等): tinyblob、blob、mediumblob、longblob
MySQL与Java一样,也有数据类型。MySQL中数据类型主要应用在列上。
常用类型:
int:整型
double:浮点型,例如double(5,2)表示最多5位,其中必须有2位小数,即最大值为999.99;
decimal:泛型型,在表单钱方面使用该类型,因为不会出现精度缺失问题;
char:固定长度字符串类型;
varchar:可变长度字符串类型;
text:字符串类型;
blob:字节类型;
date:日期类型,格式为:yyyy-MM-dd;
time:时间类型,格式为:hh:mm:ss
timestamp:时间戳类型;
与常规的脚本语言类似, MySQ 也具有一套对字符、单词以及特殊符号的使用规定, MySQ 通过执行 SQ 脚本来完成对数据库的操作, 该脚本由一条或多条MySQL语句(SQL语句 + 扩展语句)组成, 保存时脚本文件后缀名一般为 .sql。在控制台下, MySQ 客户端也可以对语句进行单句的执行而不用保存为.sql文件。
标识符用来命名一些对象, 如数据库、表、列、变量等, 以便在脚本中的其他地方引用。MySQL标识符命名规则稍微有点繁琐, 这里我们使用万能命名规则: 标识符由字母、数字或下划线(_)组成, 且第一个字符必须是字母或下划线。
对于标识符是否区分大小写取决于当前的操作系统, Windows下是不敏感的, 但对于大多数 linux\unix 系统来说, 这些标识符大小写是敏感的。
MySQL的关键字众多, 这里不一一列出, 在学习中学习。 这些关键字有自己特定的含义, 尽量避免作为标识符。
MySQL语句是组成MySQL脚本的基本单位, 每条语句能完成特定的操作, 他是由 SQ 标准语句 + MySQ 扩展语句组成。
MySQL函数用来实现数据库操作的一些高级功能, 这些函数大致分为以下几类: 字符串函数、数学函数、日期时间函数、搜索函数、加密函数、信息函数。
当 MySQ 服务已经运行时, 我们可以通过MySQL自带的客户端工具登录到MySQL数据库中, 首先打开命令提示符, 输入以下格式的命名:
mysq -h 主机名 -u 用户名 -p
-h : 该命令用于指定客户端所要登录的MySQL主机名, 登录当前机器该参数可以省略;
-u : 所要登录的用户名;
-p : 告诉服务器将会使用一个密码来登录, 如果所要登录的用户名密码为空, 可以忽略此选项。
以登录刚刚安装在本机的MySQL数据库为例, 在命令行下输入 mysq -u root -p 按回车确认, 如果安装正确且MySQL正在运行, 会得到以下响应:
Enter password:
若密码存在, 输入密码登录, 不存在则直接按回车登录, 按照本文中的安装方法, 默认 root 账号是无密码的。登录成功后你将会看到 Welecome to the MySQ monitor… 的提示语。
然后命令提示符会一直以 mysql> 加一个闪烁的光标等待命令的输入, 输入 exit 或 quit 退出登录。
使用 create database 语句可完成对数据库的创建, 创建命令的格式如下:
create database 数据库名 [其他选项];
例如我们需要创建一个名为 samp_db 的数据库, 在命令行下执行以下命令:
create database samp_db character set gbk;
要对一个数据库进行操作, 必须先选择该数据库, 否则会提示错误:
ERROR 1046(3D000): No database selected
两种方式对数据库进行使用的选择:
一: 在登录数据库时指定, 命令: mysq -D 所选择的数据库名 -h 主机名 -u 用户名 -p
例如登录时选择刚刚创建的数据库: mysq -D samp_db -u root -p
二: 在登录后使用 use 语句指定, 命令: use 数据库名;
use 语句可以不加分号, 执行 use samp_db 来选择刚刚创建的数据库, 选择成功后会提示: Database changed
使用 create table 语句可完成对表的创建, create table 的常见形式:
create table 表名称(列声明);
以创建 students 表为例, 表中将存放 学号(id)、姓名(name)、性别(sex)、年龄(age)、联系电话(tel) 这些内容:
create table students
(
id int unsigned not nul auto_increment primary key,
name char(8) not null,
sex char(4) not null,
age tinyint unsigned not null,
te char(13) nul default "-"
);
insert 语句可以用来将一行或多行数据插到数据库表中, 使用的一般形式如下:
insert [into] 表名 [(列名1, 列名2, 列名3, ...)] values (值1, 值2, 值3, ...);
其中 [] 内的内容是可选的, 例如, 要给 samp_db 数据库中的 students 表插入一条记录, 执行语句:
insert into students values(NULL, "王刚", "男", 20, "13811371377");
select 语句常用来根据一定的查询规则到数据库中获取数据, 其基本的用法为:
select 列名称 from 表名称 [查询条件];
例如要查询 students 表中所有学生的名字和年龄, 输入语句 select name, age from students; 执行结果如下:
mysql> select name, age from students;
+--------+-----+
| name | age |
+--------+-----+
| 王刚 | 20 |
| 孙丽华 | 21 |
| 王永恒 | 23 |
| 郑俊杰 | 19 |
| 陈芳 | 22 |
| 张伟朋 | 21 |
+--------+-----+
6 rows in set (0.00 sec)
按特定条件查询:
where 关键词用于指定查询条件, 用法形式为: select 列名称 from 表名称 where 条件;
以查询所有性别为女的信息为例, 输入查询语句: select * from students where sex="女";
update 语句可用来修改表中的数据, 基本的使用形式为:
update 表名称 set 列名称=新值 where 更新条件;
使用示例:
将id为5的手机号改为默认的"-": update students set tel=default where id=5;
将所有人的年龄增加1: update students set age=age+1;
将手机号为 13288097888 的姓名改为 "张伟鹏", 年龄改为 19: update students set name="张伟鹏", age=19 where tel="13288097888";
delete 语句用于删除表中的数据, 基本用法为:
delete from 表名称 where 删除条件;
使用示例:
删除id为2的行: delete from students where id=2;
删除所有年龄小于21岁的数据: delete from students where age<20;
删除表中的所有数据: delete from students;
alter table 语句用于创建后对表的修改, 基础用法如下:
基本形式: alter table 表名 add 列名 列数据类型 [after 插入位置];
示例:
在表的最后追加列 address: alter table students add address char(60);
在名为 age 的列后插入列 birthday: alter table students add birthday date after age;
基本形式: alter table 表名 change 列名称 列新名称 新数据类型;
示例:
将表 te 列改名为 telphone: alter table students change te telphone char(13) default "-";
将 name 列的数据类型改为 char(16): alter table students change name name char(16) not null;
基本形式: alter table 表名 drop 列名称;
示例:
删除 birthday 列: alter table students drop birthday;
基本形式: alter table 表名 rename 新表名;
示例:
重命名 students 表为 workmates: alter table students rename workmates;
基本形式: drop table 表名;
示例: 删除 workmates 表: drop table workmates;
删除整个数据库
基本形式: drop database 数据库名;
示例:
删除 samp_db 数据库: drop database samp_db;
按照本文的安装方式, root 用户默认是没有密码的, 重设 root 密码的方式也较多, 这里仅介绍一种较常用的方式。
使用 mysqladmin 方式:
打开命令提示符界面, 执行命令: mysqladmin -u root -p password 新密码
执行后提示输入旧密码完成密码修改, 当旧密码为空时直接按回车键确认即可。
1、说明:创建数据库
CREATE DATABASE database-name
2、说明:删除数据库
drop database dbname
3、说明:备份sq server
— 创建 备份数据的 device
USE master
EXEC sp_addumpdevice 'disk', 'testBack', 'c:\mssql7backup\MyNwind_1.dat'
— 开始 备份
BACKUP DATABASE pubs TO testBack
4、说明:创建新表
create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)
根据已有的表创建新表:
A:create table tab_new like tab_old (使用旧表创建新表)
B:create table tab_new as select col1,col2… from tab_old definition only
5、说明:删除新表
drop table tabname
6、说明:增加一个列
Alter table tabname add column co type
注:列增加后将不能删除。DB2中列加上后数据类型也不能改变,唯一能改变的是增加varchar类型的长度。
7、说明:添加主键: Alter table tabname add primary key(col)
说明:删除主键: Alter table tabname drop primary key(col)
8、说明:创建索引:create [unique] index idxname on tabname(col….)
删除索引:drop index idxname
注:索引是不可更改的,想更改必须删除重新建。
9、说明:创建视图:create view viewname as select statement
删除视图:drop view viewname
10、说明:几个简单的基本的sql语句
选择:select * from table1 where 范围
插入:insert into table1(field1,field2) values(value1,value2)
删除:delete from table1 where 范围
更新:update table1 set field1=value1 where 范围
查找:select * from table1 where field1 like ’%value1%’ ---like的语法很精妙,查资料!
排序:select * from table1 order by field1,field2 [desc]
总数:select count as totalcount from table1
求和:select sum(field1) as sumvalue from table1
平均:select avg(field1) as avgvalue from table1
最大:select max(field1) as maxvalue from table1
最小:select min(field1) as minvalue from table1
尽量避免在列上运算,这样会导致索引失效。 1.1 日期运算 优化前:
select * from system_user where date(createtime) >= '2015-06-01'
优化后:
select * from system_user where createtime >= '2015-06-01'
1.2 加,减,乘,除 优化前:
select * from system_user where age + 10 >= 20
优化后:
select * from system_user where age >= 10
用整型设计的索引,占用的字节少,相对与字符串索引要快的多。特别是创建主键索引和唯一索引的时候。 1)设计日期时候,建议用int取代char(8)。例如整型:20150603。 2)设计IP时候可以用bigint把IP转化为长整型存储。
使用join的时候,应该尽量让小结果集驱动大的结果集,把复杂的join查询拆分成多个query。因为join多个表的时候,可能会有表的锁定和阻塞。如果大结果集非常大,而且被锁了,那么这个语句会一直等待。这个也是新手常犯的一个错误! 优化前:
select
*
from table_a a
left join table_b b
on a.id = b.id
left join table_c c
on a.id = c.id
where a.id > 100
and b.id < 200
优化后:
select
*
from (
select
*
from table_a
where id > 100
) a
left join(
select
*
from table_b
where id < 200
)b
on a.id = b.id
left join table_c
on a.id = c.id
仅列出需要查询的字段,新手一般都查询的时候都是*,其实这样不好。这对速度不会有明显的影响,主要考虑的是节省内存。 优化前:
select * from system_user where age > 10
优化后:
select username,emai from system_user where age > 10
优化前:
insert into system_user(username,passwd) values('test1','123456')
insert into system_user(username,passwd) values('test2','123456')
insert into system_user(username,passwd) values('test3','123456')
优化后:
insert into system_user(username,passwd) values('test1','123456'),('test2','123456'),('test3','123456')
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
因为之前学校有学过linux😂,而且这几年一直使用的Mac做开发,所以对Linux及相关命令还是有些接触的,只是没有机会,也没有刻意去专门研究或者整理,最近因为学习与工作的而需要开始了一次Linux之路,所以学习的过程中寻找资料并且整理了一下。
目前几乎大部分的互联网公司公司都是使用Linux
这里以Ubantu为例整理一套Linux命令,至于为什么是Ubantu,谁用谁知道,这里我是在Mac上面安装了一个VM然后直接安装Ubantu,相关教程请查看网上资料。好了废话不多说,开干🤔🤔🤔🤔🤔🤔🤔🤔
查看软件xxx安装内容
#dpkg -L xxx
查找软件
#apt-cache search 正则表达式
查找文件属于哪个包
#dpkg -S filename apt-file search filename
查询软件xxx依赖哪些包
#apt-cache depends xxx
查询软件xxx被哪些包依赖
#apt-cache rdepends xxx
增加一个光盘源
#sudo apt-cdrom add
系统升级
#sudo apt-get update
#sudo apt-get upgrade
#sudo apt-get dist-upgrade
清除所以删除包的残余配置文件
#dpkg -l |grep ^rc|awk ‘{print $2}’ |tr [”"n”] [” “]|sudo xargs dpkg -P -
编译时缺少h文件的自动处理
#sudo auto-apt run ./configure
查看安装软件时下载包的临时存放目录
#ls /var/cache/apt/archives
备份当前系统安装的所有包的列表
#dpkg –get-selections | grep -v deinstall > ~/somefile
从上面备份的安装包的列表文件恢复所有包
#dpkg –set-selections < ~/somefile sudo dselect
清理旧版本的软件缓存
#sudo apt-get autoclean
清理所有软件缓存
#sudo apt-get clean
删除系统不再使用的孤立软件
#sudo apt-get autoremove
查看包在服务器上面的地址
#apt-get -qq –print-uris install ssh | cut -d"’ -f2
查看内核
#uname -a
查看Ubuntu版本
#cat /etc/issue
查看内核加载的模块
#lsmod
查看PCI设备
#lspci
查看USB设备
#lsusb
查看网卡状态
#sudo ethtool eth0
查看CPU信息
#cat /proc/cpuinfo
显示当前硬件信息
#lshw
查看硬盘的分区
#sudo fdisk -l
查看IDE硬盘信息
#sudo hdparm -i /dev/hda
查看STAT硬盘信息
#sudo hdparm -I /dev/sda
或
#sudo apt-get install blktool
#sudo blktool /dev/sda id
查看硬盘剩余空间
#df -h
#df -H
查看目录占用空间
#du -hs 目录名
优盘没法卸载
#sync fuser -km /media/usbdisk
查看当前的内存使用情况
#free -m
查看当前有哪些进程
#ps -A
中止一个进程
#kill 进程号(就是ps -A中的第一列的数字) 或者 killall 进程名
强制中止一个进程(在上面进程中止不成功的时候使用)
#kill -9 进程号 或者 killall -9 进程名
图形方式中止一个程序
#xkill 出现骷髅标志的鼠标,点击需要中止的程序即可
查看当前进程的实时状况
#top
查看进程打开的文件
#lsof -p
ADSL 配置 ADSL
#sudo pppoeconf
ADSL手工拨号
#sudo pon dsl-provider
激活 ADSL
#sudo /etc/ppp/pppoe_on_boot
断开 ADSL
#sudo poff
查看拨号日志
#sudo plog
如何设置动态域名
#首先去http://www.3322.org申请一个动态域名
#然后修改 /etc/ppp/ip-up 增加拨号时更新域名指令 sudo vim /etc/ppp/ip-up
#在最后增加如下行 w3m -no-cookie -dump
根据IP查网卡地址
#arping IP地址
查看当前IP地址
#ifconfig eth0 |awk ‘/inet/ {split($2,x,”:”);print x[2]}’
查看当前外网的IP地址
#w3m -no-cookie -dumpwww.edu.cn|grep-o‘[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}’
#w3m -no-cookie -dumpwww.xju.edu.cn|grep-o’[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}’
#w3m -no-cookie -dump ip.loveroot.com|grep -o’[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}".[0-9]"{1,3"}’
查看当前监听80端口的程序
#lsof -i :80
查看当前网卡的物理地址
#arp -a | awk ‘{print $4}’ ifconfig eth0 | head -1 | awk ‘{print $5}’
立即让网络支持nat
#sudo echo 1 > /proc/sys/net/ipv4/ip_forward
#sudo iptables -t nat -I POSTROUTING -j MASQUERADE
查看路由信息
#netstat -rn sudo route -n
手工增加删除一条路由
#sudo route add -net 192.168.0.0 netmask 255.255.255.0 gw 172.16.0.1
#sudo route del -net 192.168.0.0 netmask 255.255.255.0 gw 172.16.0.1
修改网卡MAC地址的方法
#sudo ifconfig eth0 down 关闭网卡
#sudo ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 然后改地址
#sudo ifconfig eth0 up 然后启动网卡
统计当前IP连接的个数
#netstat -na|grep ESTABLISHED|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -r -n
#netstat -na|grep SYN|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -r -n
统计当前20000个IP包中大于100个IP包的IP地址
#tcpdump -tnn -c 20000 -i eth0 | awk -F “.” ‘{print $1″.”$2″.”$3″.”$4}’ | sort | uniq -c | sort -nr | awk ‘ $1 > 100 ‘
屏蔽IPV6
#echo “blacklist ipv6″ | sudo tee /etc/modprobe.d/blacklist-ipv6
添加一个服务
#sudo update-rc.d 服务名 defaults 99
删除一个服务
#sudo update-rc.d 服务名 remove
临时重启一个服务
#/etc/init.d/服务名 restart
临时关闭一个服务
#/etc/init.d/服务名 stop
临时启动一个服务 #/etc/init.d/服务名 start
配置默认Java使用哪个
#sudo update-alternatives –config java
修改用户资料
#sudo chfn userid
给apt设置代理
#export http_proxy=http://xx.xx.xx.xx:xxx
修改系统登录信息
#sudo vim /etc/motd
转换文件名由GBK为UTF8
#sudo apt-get install convmv convmv -r -f cp936 -t utf8 –notest –nosmart *
批量转换src目录下的所有文件内容由GBK到UTF8
#find src -type d -exec mkdir -p utf8/{} "; find src -type f -exec iconv -f GBK -t UTF-8 {} -o utf8/{} "; mv utf8/* src rm -fr utf8
转换文件内容由GBK到UTF8
#iconv -f gbk -t utf8 $i > newfile
转换 mp3 标签编码
#sudo apt-get install python-mutagen find . -iname “*.mp3” -execdir mid3iconv -e GBK {} ";
控制台下显示中文
#sudo apt-get install zhcon 使用时,输入zhcon即可
快速查找某个文件
#whereis filename
#find 目录 -name 文件名
查看文件类型
#file filename
显示xxx文件倒数6行的内容
#tail -n 6 xxx
让tail不停地读地最新的内容
#tail -n 10 -f /var/log/apache2/access.log
查看文件中间的第五行(含)到第10行(含)的内容
#sed -n ‘5,10p’ /var/log/apache2/access.log
查找包含xxx字符串的文件
#grep -l -r xxx .
全盘搜索文件(桌面可视化)
gnome-search-tool
查找关于xxx的命令
#apropos xxx man -k xxx
通过ssh传输文件
#scp -rp /path/filenameusername@remoteIP:/path
#将本地文件拷贝到服务器上
#scp -rpusername@remoteIP:/path/filename/path
#将远程文件从服务器下载到本地
查看某个文件被哪些应用程序读写
#lsof 文件名
把所有文件的后辍由rm改为rmvb
#rename ’s/.rm$/.rmvb/’ *
把所有文件名中的大写改为小写
#rename ‘tr/A-Z/a-z/’ *
删除特殊文件名的文件,如文件名:–help.txt
#rm — –help.txt 或者 rm ./–help.txt
查看当前目录的子目录
#ls -d */. 或 echo */.
将当前目录下最近30天访问过的文件移动到上级back目录
#find . -type f -atime -30 -exec mv {} ../back ";
将当前目录下最近2小时到8小时之内的文件显示出来
#find . -mmin +120 -mmin -480 -exec more {} ";
删除修改时间在30天之前的所有文件
#find . -type f -mtime +30 -mtime -3600 -exec rm {} ";
查找guest用户的以avi或者rm结尾的文件并删除掉
#find . -name ‘*.avi’ -o -name ‘*.rm’ -user ‘guest’ -exec rm {} ";
查找的不以java和xml结尾,并7天没有使用的文件删除掉
#find . ! -name *.java ! -name ‘*.xml’ -atime +7 -exec rm {} ";
统计当前文件个数
#ls /usr/bin|wc -w
统计当前目录个数
#ls -l /usr/bin|grep ^d|wc -l
显示当前目录下2006-01-01的文件名
#ls -l |grep 2006-01-01 |awk ‘{print $8}’
上传下载文件工具-filezilla
#sudo apt-get install filezilla
filezilla无法列出中文目录? 站点->字符集->自定义->输入:GBK
1)下载filezilla中文包到本地目录,如~/ 2)#unrar x Filezilla3_zhCN.rar 3) 如果你没有unrar的话,请先安装rar和unrar
#sudo apt-get install rar unrar
#sudo ln -f /usr/bin/rar /usr/bin/unrar
4)先备份原来的语言包,再安装;实际就是拷贝一个语言包。
#sudo cp /usr/share/locale/zh_CN/filezilla.mo /usr/share/locale/zh_CN/filezilla.mo.bak
#sudo cp ~/locale/zh_CN/filezilla.mo /usr/share/locale/zh_CN/filezilla.mo
5)重启filezilla,即可!
解压缩 xxx.tar.gz
#tar -zxvf xxx.tar.gz
解压缩 xxx.tar.bz2
#tar -jxvf xxx.tar.bz2
压缩aaa bbb目录为xxx.tar.gz
#tar -zcvf xxx.tar.gz aaa bbb
压缩aaa bbb目录为xxx.tar.bz2
#tar -jcvf xxx.tar.bz2 aaa bbb
1) 先安装
#sudo apt-get install rar unrar
#sudo ln -f /usr/bin/rar /usr/bin/unrar
2) 解压
#unrar x aaaa.rar
1) 先安装
#sudo apt-get install zip unzip
#sudo ln -f /usr/bin/zip /usr/bin/unzip
2) 解压
#unzip x aaaa.zip
显示隐藏文件
Ctrl+h
显示地址栏
Ctrl+l
特殊 URI 地址
* computer:/// - 全部挂载的设备和网络
* network:/// - 浏览可用的网络
* burn:/// - 一个刻录 CDs/DVDs 的数据虚拟目录
* smb:/// - 可用的 windows/samba 网络资源
* x-nautilus-desktop:/// - 桌面项目和图标
*file:///- 本地文件
* trash:/// - 本地回收站目录
* ftp:// - FTP 文件夹
* ssh:// - SSH 文件夹
* fonts:/// - 字体文件夹,可将字体文件拖到此处以完成安装
* themes:/// - 系统主题文件夹
查看已安装字体
在nautilus的地址栏里输入”fonts:///“,就可以查看本机所有的fonts
详细显示程序的运行信息
#strace -f -F -o outfile
设置日期
#date -s mm/dd/yy
设置时间
#date -s HH:MM
将时间写入CMOS
#hwclock –systohc
读取CMOS时间
#hwclock –hctosys
从服务器上同步时间
#sudo ntpdate time.nist.gov
#sudo ntpdate time.windows.com
不同控制台间切换
Ctrl + ALT + ← Ctrl + ALT + →
指定控制台切换
Ctrl + ALT + Fn(n:1~7)
控制台下滚屏
SHIFT + pageUp/pageDown
控制台抓图
#setterm -dump n(n:1~7)
mysql的数据库存放在地方
#/var/lib/mysql
从mysql中导出和导入数据
#mysqldump 数据库名 > 文件名 #导出数据库
#mysqladmin create 数据库名 #建立数据库
#mysql 数据库名 < 文件名 #导入数据库
忘了mysql的root口令怎么办
#sudo /etc/init.d/mysql stop
#sudo mysqld_safe –skip-grant-tables
#sudo mysqladmin -u user password ‘newpassword”
#sudo mysqladmin flush-privileges
修改mysql的root口令
#sudo mysqladmin -uroot -p password ‘你的新密码’
下载网站文档
#wget -r -p -np -khttp://www.21cn.com
· r:在本机建立服务器端目录结构;
· -p: 下载显示HTML文件的所有图片;
· -np:只下载目标站点指定目录及其子目录的内容;
· -k: 转换非相对链接为相对链接。
如何删除Totem电影播放机的播放历史记录
#rm ~/.recently-used
如何更换gnome程序的快捷键 点击菜单,鼠标停留在某条菜单上,键盘输入任意你所需要的键,可以是组合键,会立即生效; 如果要清除该快捷键,请使用backspace
vim 如何显示彩色字符
#sudo cp /usr/share/vim/vimcurrent/vimrc_example.vim /usr/share/vim/vimrc
如何在命令行删除在会话设置的启动程序
#cd ~/.config/autostart rm 需要删除启动程序
如何提高wine的反应速度
#sudo sed -ie ‘/GBK/,/^}/d’ /usr/share/X11/locale/zh_CN.UTF-8/XLC_LOCALE
#chgrp
语法: chgrp [-R] 文件组 文件… 说明: 文件的GID表示文件的文件组,文件组可用数字表示, 也可用一个有效的组名表示,此命令改变一个文件的GID,可参看chown。 -R 递归地改变所有子目录下所有文件的存取模式 例子:
#chgrp group file 将文件 file 的文件组改为 group
#chmod
语法: chmod [-R] 模式 文件… 或 chmod [ugoa] {+|-|=} [rwxst] 文件… 说明: 改变文件的存取模式,存取模式可表示为数字或符号串,例如: #chmod nnnn file , n为0-7的数字,意义如下:
4000 运行时可改变UID
2000 运行时可改变GID
1000 置粘着位
0400 文件主可读
0200 文件主可写
0100 文件主可执行
0040 同组用户可读
0020 同组用户可写
0010 同组用户可执行
0004 其他用户可读
0002 其他用户可写
0001 其他用户可执行
nnnn 就是上列数字相加得到的,例如 chmod 0777 file 是指将文件 file 存取权限置为所有用户可读可写可执行。 -R 递归地改变所有子目录下所有文件的存取模式
u 文件主
g 同组用户
o 其他用户
a 所有用户
+ 增加后列权限
- 取消后列权限
= 置成后列权限
r 可读
w 可写
x 可执行
s 运行时可置UID
t 运行时可置GID
例子:
#chmod 0666 file1 file2 将文件 file1 及 file2 置为所有用户可读可写
#chmod u+x file 对文件 file 增加文件主可执行权限
#chmod o-rwx 对文件file 取消其他用户的所有权限
#chown
语法: chown [-R] 文件主 文件…
说明: 文件的UID表示文件的文件主,文件主可用数字表示, 也可用一个有效的用户名表示,此命令改变一个文件的UID,仅当此文件的文件主或超级用户可使用。 -R 递归地改变所有子目录下所有文件的存取模式 例子:
#chown mary file 将文件 file 的文件主改为 mary
#chown 150 file 将文件 file 的UID改为150
以eth0为例
编辑文件/etc/network/interfaces:
#sudo vi /etc/network/interfaces
并用下面的行来替换有关eth0的行:
# The primary network interface - use DHCP to find our address
auto eth0
iface eth0 inet dhcp
用下面的命令使网络设置生效:
#sudo /etc/init.d/networking restart
当然,也可以在命令行下直接输入下面的命令来获取地址
#sudo dhclient eth0
编辑文件/etc/network/interfaces:
#sudo vi /etc/network/interfaces
并用下面的行来替换有关eth0的行:
# The primary network interface
auto eth0
iface eth0 inet static
address 192.168.3.90
gateway 192.168.3.1
netmask 255.255.255.0
network 192.168.3.0
broadcast 192.168.3.255
将上面的ip地址等信息换成你自己就可以了.
用下面的命令使网络设置生效:
#sudo /etc/init.d/networking restart
编辑文件/etc/network/interfaces:
#sudo vi /etc/network/interfaces
在该文件中添加如下的行:
auto eth0:1
iface eth0:1 inet static
address 192.168.1.60
netmask 255.255.255.0
network x.x.x.x
broadcast x.x.x.x
gateway x.x.x.x
根据你的情况填上所有诸如address,netmask,network,broadcast和gateways等信息. 用下面的命令使网络设置生效:
#sudo /etc/init.d/networking restart
使用下面的命令来查看当前主机的主机名称:
#sudo /bin/hostname
使用下面的命令来设置当前主机的主机名称:
#sudo /bin/hostname newname
系统启动时,它会从/etc/hostname来读取主机的名称.
首先,你可以在/etc/hosts中加入一些主机名称和这些主机名称对应的IP地址,这是简单使用本机的静态查询. 要访问DNS 服务器来进行查询,需要设置/etc/resolv.conf文件. 假设DNS服务器的IP地址是192.168.3.2, 那么/etc/resolv.conf文件的内容应为:
search test.com
nameserver 192.168.3.2
如果采用Ubuntu Server CD开始安装时,可以选择安装,这系统会自动装上apache2,php5和mysql5。下面主要说明一下如果不是安装的Ubuntu server时的安装方法。 用命令在Ubuntu下架设Lamp其实很简单,用一条命令就完成。在终端输入以下命令:
#sudo apt-get install apache2 mysql-server php5 php5-mysql php5-gd #phpmyadmin
装好后,mysql管理员是root,无密码,通过http://localhost/phpmyadmin%E5%B0%B1%E5%8F%AF%E4%BB%A5%E8%AE%BF%E9%97%AEmysql%E4%BA%86
终端下输入:
#mysql -u root
#mysql> GRANT ALL PRIVILEGES ON *.* TO root@localhost IDENTIFIED BY “123456″;
’123456‘是root的密码,可以自由设置,但最好是设个安全点的。 #mysql> quit; 退出mysql
启动:#sudo /etc/init.d/apache2 start
重启:#sudo /etc/init.d/apache2 restart
关闭:#sudo /etc/init.d/apache2 stop
apache2的默认主目录:/var/www/
ls 列出当前目录文件(不包括隐含文件)
ls -a 列出当前目录文件(包括隐含文件)
ls -l 列出当前目录下文件的详细信息
cd .. 回当前目录的上一级目录
cd - 回上一次所在的目录
cd ~ 或 cd 回当前用户的宿主目录
mkdir 目录名 创建一个目录
rmdir 空目录名 删除一个空目录
rm 文件名 文件名 删除一个文件或多个文件
rm -rf 非空目录名 删除一个非空目录下的一切
mv 路经/文件 /经/文件移动相对路经下的文件到绝对路经下
mv 文件名 新名称 在当前目录下改名
find 路经 -name “字符串” 查找路经所在范围内满足字符串匹配的文件和目录
fdisk fdisk -l 查看系统分区信息
fdisk fdisk /dev/sdb 为一块新的SCSI硬盘进行分区
chown chown root /home 把/home的属主改成root用户
chgrp chgrp root /home 把/home的属组改成root组
Useradd 创建一个新的用户
Groupadd 组名 创建一个新的组
Passwd 用户名 为用户创建密码
Passwd -d用户名 删除用户密码也能登陆
Passwd -S用户名 查询账号密码
Usermod -l 新用户名 老用户名 为用户改名
Userdel–r 用户名 删除用户一切
service [servicename] start/stop/restart 系统服务控制操作
/etc/init.d/[servicename] start/stop/restart 系统服务控制操作
uname -a 查看内核版本
cat /etc/issue 查看ubuntu版本
lsusb 查看usb设备
sudo ethtool eth0 查看网卡状态
cat /proc/cpuinfo 查看cpu信息
lshw 查看当前硬件信息
sudo fdisk -l 查看磁盘信息
df -h 查看硬盘剩余空间
free -m 查看当前的内存使用情况
ps -A 查看当前有哪些进程
kill 进程号(就是ps -A中的第一列的数字)或者 killall 进程名( 杀死一个进程)
kill -9 进程号 强制杀死一个进程
reboot Init 6 重启LINUX系统
Halt Init 0 Shutdown –h now 关闭LINUX系统
tar -c 创建包 –x 释放包 -v 显示命令过程 –z 代表压缩包
tar –cvf benet.tar /home/benet 把/home/benet目录打包
tar –zcvf benet.tar.gz /mnt 把目录打包并压缩
tar –zxvf benet.tar.gz 压缩包的文件解压恢复
tar –jxvf benet.tar.bz2 解压缩
make 编译
make install 安装编译好的源码包
apt-cache search package 搜索包
apt-cache show package 获取包的相关信息,如说明、大小、版本等
sudo apt-get install package 安装包
sudo apt-get install package - - reinstall 重新安装包
sudo apt-get -f install 修复安装”-f = –fix-missing”
sudo apt-get remove package 删除包
sudo apt-get remove package - - purge 删除包,包括删除配置文件等
sudo apt-get update 更新源
sudo apt-get upgrade 更新已安装的包
sudo apt-get dist-upgrade 升级系统
sudo apt-get dselect-upgrade 使用 dselect 升级
apt-cache depends package 了解使用依赖
apt-cache rdepends package 是查看该包被哪些包依赖
sudo apt-get build-dep package 安装相关的编译环境
apt-get source package 下载该包的源代码
sudo apt-get clean && sudo apt-get autoclean 清理无用的包
sudo apt-get check 检查是否有损坏的依赖
sudo apt-get clean 清理所有软件缓存(即缓存在/var/cache/apt/archives目录里的deb包)
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
获取ThinkPHP的方式很多,官方网站(http://thinkphp.cn)提供了稳定版本或者带扩展完整版本的下载。
官网的下载版本不一定是最新版本,GIT版本获取的才是保持更新的版本。
ThinkPHP5支持使用Composer安装,如果还没有安装 Composer,你可以按 Composer安装 中的方法安装。在 Linux 和 Mac OS X 中可以运行如下命令:
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
然后在命令行下面,切换到你的web根目录下面并执行下面的命令:
composer create-project topthink/think tp5 --prefer-dist
如果你不太了解Composer或者觉得Composer太慢,也可以使用git版本库安装和更新,ThinkPHP5.0拆分为多个仓库,主要包括:
应用项目:https://github.com/top-think/think
核心框架:https://github.com/top-think/framework
之所以设计为应用和核心仓库分离,是为了支持Composer单独更新核心框架。
首先克隆下载应用项目仓库
git clone https://github.com/top-think/think tp5
然后切换到tp5目录下面,再克隆核心框架仓库:
git clone https://github.com/top-think/framework thinkphp
两个仓库克隆完成后,就完成了ThinkPHP5.0的Git方式下载,如果需要更新核心框架的时候,只需要切换到thinkphp核心目录下面,然后执行:
git pull https://github.com/top-think/framework
如果不熟悉git命令行,可以使用任何一个GIT客户端进行操作,在此不再详细说明。
无论你采用什么方式获取的ThinkPHP框架,现在只需要做最后一步来验证是否正常运行。
在浏览器中输入地址:
http://localhost/tp5/public/
如果浏览器输出如图所示,那么说明你成功了,如果不是,那么请检测相关配置或者步骤:
ThinkPHP5遵循PSR-2命名规范和PSR-4自动加载规范,并且注意如下规范:
目录不强制规范,驼峰及小写+下划线模式均支持;
类库、函数文件统一以.php为后缀;
类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
类文件采用驼峰法命名(首字母大写),其它文件采用小写+下划线命名;
类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
类的命名采用驼峰法(首字母大写),例如 User、UserType,默认不需要添加后缀,例如UserController应该直接命名为User;
函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip;
方法的命名使用驼峰法(首字母小写),例如 getUserName;
属性的命名使用驼峰法(首字母小写),例如 tableName、instance;
以双下划线“__”打头的函数或方法作为魔法方法,例如 __call 和 __autoload;
常量以大写字母和下划线命名,例如 APP_PATH和 THINK_PATH;
配置参数以小写字母和下划线命名,例如 url_route_on 和url_convert;
数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user 表和 user_name字段,不建议使用驼峰和中文作为数据表字段命名。
应用类库的根命名空间统一为app(可以设置app_namespace配置参数更改)
例如:app\index\controller\Index和app\index\model\User。
下载最新版框架后,解压缩到web目录下面,可以看到初始的目录结构如下:
5.0的部署建议是public目录作为web目录访问内容,其它都是web目录之外,当然,你必须要修改public/index.php中的相关路径。如果没法做到这点,请记得设置目录的访问权限或者添加目录列表的保护文件。
router.php用于php自带webserver支持,可用于快速测试
启动命令:php -S localhost:8888 router.php
5.0版本自带了一个完整的应用目录结构和默认的应用入口文件,开发人员可以在这个基础之上灵活调整。
上面的目录结构和名称是可以改变的,尤其是应用的目录结构,这取决于你的入口文件和配置参数。
这里是我下载解压后的目录结构
细心的话我们可以发现,ThinkPhp5中入口文件相对ThinkPhp3中的入口文件发生了变化,在ThinkPhp5中的入口文件是放在根目录中的public里面index.php文件,其实5相对3来说,变化还是挺大的,如果之前是使用3写的项目。要升级到5的话,估计也是一个大工程,需要很细心的处理。
查看public中可以看到index.php的内容和ThinkPhp3中的入口有点相似:
ThinkPhp3的入口文件
// 应用入口文件
// 检测PHP环境
if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !');
// 开启调试模式 建议开发阶段开启 部署阶段注释或者设为false
define('APP_DEBUG',True);
// 定义应用目录
define('APP_NAME','App');
// 定义应用目录
define('APP_PATH','./App/');
// 引入ThinkPHP入口文件
require './ThinkPHP/ThinkPHP.php';
// 亲^_^ 后面不需要任何代码了 就是如此简单
ThinkPhp5的入口文件
// [ 应用入口文件 ]
// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
所以我们会发现入口文件指向的是Application文件夹,然后进入到Application文件夹,有个index文件夹,里面有个Controller文件夹,打开index.php文件,看到:
<?php
namespace app\index\controller;
class Index
{
public function index()
{
return '<style type="text/css">*{ padding: 0; margin: 0; } .think_default_text{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p> ThinkPHP V5<br/><span style="font-size:30px">十年磨一剑 - 为API开发设计的高性能框架</span></p><span style="font-size:22px;">[ V5.0 版本由 <a href="http://www.qiniu.com" target="qiniu">七牛云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="http://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="http://ad.topthink.com/Public/static/client.js"></script><thinkad id="ad_bd568ce7058a1091"></thinkad>';
}
}
其中return返回的数据,正好是我们前面输入:http://localhost/tp5/public/所看到对应的数据。
下面开始简单的项目文件配置(根据个人习惯而定)。
一切先从Application文件夹开始。因为我们平时一个项目中都会分前后端,所以这里在Application中新建一些文件个文件夹,至于index文件夹可以不用管,也可以在他的基础上或者直接把他当做前段文件夹。
home文件夹
admin文件夹
app_extend文件夹
common文件夹
关于其他配置和相关文件的介绍这里就略过了。相信有一点基础一应都能看懂。
index.php实现
<?php
namespace app\home\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
return $this->fetch();
}
}
fetch()和ThinkPhp3中的display()方法的功能一应,实现MV层的传递。
fetch 渲染模板输出
display 渲染内容输出
assign 模板变量赋值
engine 初始化模板引擎
index.html实现
<html>
<head>
<meta charset = "utf-8">
</head>
<body>
欢迎来到iCocos=name;;;;;;;;;;;
</body>
</html>
至于视图的实例化和相关配置,使用和渲染请查看官方手册:视图
使用:浏览器分别输入下面的路径,可以看到对应我们想要看到的界面
home:
http://127.0.0.1/ThComp3/public/home/index
admin:
http://127.0.0.1/ThComp3/public/admin/index
这里就简单的实现了MVC中M层和V层之间的处理。
在这之前先回顾一下原生PHP什么实现这个操作。
面向对象
<?php
$servername = "localhost";
$username = "username";
$password = "password";
// 创建连接
$conn = new mysqli($servername, $username, $password);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
echo "连接成功";
?>
PDO实现
<?php
$servername = "localhost";
$username = "username";
$password = "password";
try {
$conn = new PDO("mysql:host=$servername;dbname=myDB", $username, $password);
echo "连接成功";
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
关闭
$conn->close();
$conn = null;
<?php
$servername = "localhost";
$username = "username";
$password = "password";
// 创建连接
$conn = new mysqli($servername, $username, $password);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 创建数据库
$sql = "CREATE DATABASE myDB";
if ($conn->query($sql) === TRUE) {
echo "数据库创建成功";
} else {
echo "Error creating database: " . $conn->error;
}
$conn->close();
?>
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 使用 sql 创建数据表
$sql = "CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)";
if ($conn->query($sql) === TRUE) {
echo "Table MyGuests created successfully";
} else {
echo "创建数据表错误: " . $conn->error;
}
$conn->close();
?>
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$sql = "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('John', 'Doe', 'john@example.com')";
if ($conn->query($sql) === TRUE) {
echo "新记录插入成功";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
$conn->close();
?>
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// 创建链接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查链接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$sql = "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('John', 'Doe', 'john@example.com');";
$sql .= "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('Mary', 'Moe', 'mary@example.com');";
$sql .= "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('Julie', 'Dooley', 'julie@example.com')";
if ($conn->multi_query($sql) === TRUE) {
echo "新记录插入成功";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
$conn->close();
?>
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检测连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
$sql = "SELECT id, firstname, lastname FROM MyGuests";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
// 输出每行数据
while($row = $result->fetch_assoc()) {
echo "<br> id: ". $row["id"]. " - Name: ". $row["firstname"]. " " . $row["lastname"];
}
} else {
echo "0 个结果";
}
$conn->close();
?>
<?php
$con=mysqli_connect("localhost","username","password","database");
// 检测连接
if (mysqli_connect_errno())
{
echo "连接失败: " . mysqli_connect_error();
}
mysqli_query($con,"UPDATE Persons SET Age=36
WHERE FirstName='Peter' AND LastName='Griffin'");
mysqli_close($con);
?>
<?php
$con=mysqli_connect("localhost","username","password","database");
// 检测连接
if (mysqli_connect_errno())
{
echo "连接失败: " . mysqli_connect_error();
}
mysqli_query($con,"DELETE FROM Persons WHERE LastName='Griffin'");
mysqli_close($con);
?>
下面的实例展示了如何首先创建一个数据库连接,接着创建一个结果集,然后在 HTML 表格中显示数据。
<html>
<body>
<?php
$conn=odbc_connect('northwind','','');
if (!$conn)
{
exit("连接失败: " . $conn);
}
$sql="SELECT * FROM customers";
$rs=odbc_exec($conn,$sql);
if (!$rs)
{
exit("SQL 语句错误");
}
echo "<table><tr>";
echo "<th>Companyname</th>";
echo "<th>Contactname</th></tr>";
while (odbc_fetch_row($rs))
{
$compname=odbc_result($rs,"CompanyName");
$conname=odbc_result($rs,"ContactName");
echo "<tr><td>$compname</td>";
echo "<td>$conname</td></tr>";
}
odbc_close($conn);
echo "</table>";
?>
</body>
</html>
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
return [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'WWWiCocos',
// 用户名
'username' => 'root',
// 密码
'password' => '',
// 端口
'hostport' => '3306',
// 连接dsn
'dsn' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => '',
// 数据库调试模式
'debug' => true,
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 数据集返回类型 array 数组 collection Collection对象
'resultset_type' => 'array',
// 是否自动写入时间戳字段
'auto_timestamp' => false,
// 是否需要进行SQL性能分析
'sql_explain' => false,
];
Db::connect([
// 数据库类型
'type' => 'mysql',
// 数据库连接DSN配置
'dsn' => '',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'thinkphp',
// 数据库用户名
'username' => 'root',
// 数据库密码
'password' => '',
// 数据库连接端口
'hostport' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'think_',
]);
Db::connect('mysql://root:1234@127.0.0.1:3306/thinkphp#utf8');
首先必须清楚的明白之前在ThinkPhp3中的单字母函数,ThinkPhp5中已经取消了,比如M(),D()等,所以我们必须忘掉之前的这种风格。
这里配置好了数据库之后,使用对应的函数来验证连接,并且打印相关信息。
ThinkPhp3的方式
$db = M('tp_user');
print("<pre>"); // 格式化输出数组
var_dump($db);
print("</pre>");
ThinkPhp5的方式
print("<pre>"); // 格式化输出数组
var_dump(Db::table('tp_user')->select());
// Db::name('user')->select(); // 或者
print("</pre>");
这样就可以打印出数据库相关的信息如下:
array(2) {
[0]=>
array(5) {
["id"]=>
int(1)
["uname"]=>
string(2) "56"
["upwd"]=>
string(2) "65"
["ip"]=>
string(3) "645"
["last_time"]=>
int(564)
}
[1]=>
array(5) {
["id"]=>
int(2)
["uname"]=>
string(3) "234"
["upwd"]=>
string(3) "wer"
["ip"]=>
string(3) "221"
["last_time"]=>
int(23)
}
}
配置了数据库连接信息后,我们就可以直接使用数据库运行原生SQL操作了,支持query(查询操作)和execute(写入操作)方法,并且支持参数绑定。
Db::query('select * from think_user where id=?',[8]);
Db::execute('insert into think_user (id, name) values (?, ?)',[8,'thinkphp']);
也支持命名占位符绑定,例如:
Db::query('select * from think_user where id=:id',['id'=>8]);
Db::execute('insert into think_user (id, name) values (:id, :name)',['id'=>8,'name'=>'thinkphp']);
可以使用多个数据库连接,使用
Db::connect($config)->query('select * from think_user where id=:id',['id'=>8]);
config是一个单独的数据库配置,支持数组和字符串,也可以是一个数据库连接的配置参数名。
查询一个数据使用:
// table方法必须指定完整的数据表名
Db::table('think_user')->where('id',1)->find();
查询数据集使用:
Db::table('think_user')->where('status',1)->select();
select 方法查询结果不存在,返回空数组
如果设置了数据表前缀参数的话,可以使用
Db::name('user')->where('id',1)->find();
Db::name('user')->where('status',1)->select();
使用 Db 类的 insert 方法向数据库提交数据
$data = ['foo' => 'bar', 'bar' => 'foo'];
Db::table('think_user')->insert($data);
如果你在database.php配置文件中配置了数据库前缀(prefix),那么可以直接使用 Db 类的 name 方法提交数据
Db::name('user')->insert($data);
添加数据后如果需要返回新增数据的自增主键,可以使用getLastInsID方法:
Db::name('user')->insert($data);
$userId = Db::name('user')->getLastInsID();
或者直接使用insertGetId方法新增数据并返回主键值:
Db::name('user')->insertGetId($data);
Db::table('think_user')
->where('id', 1)
->update(['name' => 'thinkphp']);
如果数据中包含主键,可以直接使用:
Db::table('think_user')
->update(['name' => 'thinkphp','id'=>1]);
如果要更新的数据需要使用SQL函数或者其它字段,可以使用下面的方式:
Db::table('think_user')
->where('id', 1)
->update([
'login_time' => ['exp','now()'],
'login_times' => ['exp','login_times+1'],
]);
更新某个字段的值:
Db::table('think_user')
->where('id',1)
->setField('name', 'thinkphp');
// 根据主键删除
Db::table('think_user')->delete(1);
Db::table('think_user')->delete([1,2,3]);
// 条件删除
Db::table('think_user')->where('id',1)->delete();
Db::table('think_user')->where('id','<',10)->delete();
助手函数
// 根据主键删除
db('user')->delete(1);
// 条件删除
db('user')->where('id',1)->delete();
可以使用where方法进行AND条件查询:
Db::table('think_user')
->where('name','like','%thinkphp')
->where('status',1)
->find();
多字段相同条件的AND查询可以简化为如下方式:
Db::table('think_user')
->where('name&title','like','%thinkphp')
->find();
使用whereOr方法进行OR查询:
Db::table('think_user')
->where('name','like','%thinkphp')
->whereOr('title','like','%thinkphp')
->find();
多字段相同条件的OR查询可以简化为如下方式:
Db::table('think_user')
->where('name|title','like','%thinkphp')
->find();
其他高级操作参考官方手册:数据库操作
如果在某个模型类里面定义了connection属性的话,则该模型操作的时候会自动连接给定的数据库连接,而不是配置文件中设置的默认连接信息,通常用于某些数据表位于当前数据库连接之外的其它数据库,例如:
//在模型里单独设置数据库连接信息
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $connection = [
// 数据库类型
'type' => 'mysql',
// 数据库连接DSN配置
'dsn' => '',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'thinkphp',
// 数据库用户名
'username' => 'root',
// 数据库密码
'password' => '',
// 数据库连接端口
'hostport' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'think_',
];
}
也可以采用DSN字符串方式定义,例如:
//在模型里单独设置数据库连接信息
namespace app\index\model;
use think\Model;
class User extends Model
{
//或者使用字符串定义
protected $connection = 'mysql://root:1234@127.0.0.1:3306/thinkphp#utf8';
}
和连接数据库的参数一样,connection属性的值也可以设置为数据库的配置参数。
5.0不支持单独设置当前模型的数据表前缀。
模型类可以使用静态调用或者实例化调用两种方式,例如:
// 静态调用
$user = User::get(1);
$user->name = 'thinkphp';
$user->save();
// 实例化模型
$user = new User;
$user->name= 'thinkphp';
$user->save();
// 使用 Loader 类实例化(单例)
$user = Loader::model('User');
// 或者使用助手函数`model`
$user = model('User');
$user->name= 'thinkphp';
$user->save();
模型同样支持初始化,与控制器的初始化不同的是,模型的初始化是重写Model的initialize,具体如下
namespace app\index\model;
use think\Model;
class Index extends Model
{
//自定义初始化
protected function initialize()
{
//需要调用`Model`的`initialize`方法
parent::initialize();
//TODO:自定义的初始化
}
}
同样也可以使用静态init方法,需要注意的是init只在第一次实例化的时候执行,并且方法内需要注意静态调用的规范,具体如下:
namespace app\index\model;
use think\Model;
class Index extends Model
{
//自定义初始化
protected static function init()
{
//TODO:自定义的初始化
}
}
第一种是实例化模型对象后赋值并保存:
$user = new User;
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
也可以使用data方法批量赋值:
$user = new User;
$user->data([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
$user->save();
或者直接在实例化的时候传入数据
$user = new User([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
]);
$user->save();
如果需要过滤非数据表字段的数据,可以使用:
$user = new User($_POST);
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save();
如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:
$user = new User($_POST);
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save();
save方法新增数据返回的是写入的记录数。
在取出数据后,更改字段内容后更新数据。
$user = User::get(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
也可以直接带更新条件来更新数据
$user = new User;
// save方法第二个参数为更新条件
$user->save([
'name' => 'thinkphp',
'email' => 'thinkphp@qq.com'
],['id' => 1]);
上面两种方式更新数据,如果需要过滤非数据表字段的数据,可以使用:
$user = new User();
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST,['id' => 1]);
如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:
$user = new User();】(['name','email'])->save($_POST, ['id' => 1]);
删除模型数据,可以在实例化后调用delete方法。
$user = User::get(1);
$user->delete();
或者直接调用静态方法
User::destroy(1);
// 支持批量删除多个数据
User::destroy('1,2,3');
// 或者
User::destroy([1,2,3]);
使用数组进行条件删除,例如:
// 删除状态为0的数据
User::destroy(['status' => 0]);
还支持使用闭包删除,例如:
User::destroy(function($query){
$query->where('id','>',10);
});
或者通过数据库类的查询条件删除
User::where('id','>',10)->delete();
获取单个数据的方法包括:
取出主键为1的数据
$user = User::get(1);
echo $user->name;
// 使用数组查询
$user = User::get(['name' => 'thinkphp']);
// 使用闭包查询
$user = User::get(function($query){
$query->where('name', 'thinkphp');
});
echo $user->name;
或者在实例化模型后调用查询方法
$user = new User();
// 查询单个数据
$user->where('name', 'thinkphp')
->find();
其他高级操作参考官方手册:模型操作
连接并获取数据相关数据
$result = Db::table('tp_user')->find();
//$result = Db::table('tp_user')->where('upwd', 'wer')->find();
//var_dump(Db::select(function ($query)
//{
// $query->table('tp_user')->where('id', 2);
//
//}));
将获取到的数据库数据赋值给View(注意这里使用assign,thin3中是display)
$this->assign('result',$result);
return $this->fetch();
在View中拿到数据显示
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
{$result.id}--{$result.data}
</body>
</html>
一个最简单的MVC模式就实现了,其实后面开发和实战中基本上就是讲这个实现扩大,然后做相应的逻辑或者细节处理。
因为研究TP5时间不是很长,暂时先列以下几处差异:
1、过去的单字母函数已完全被替换掉,如下:
S=>cache,C=>config,M/D=>model,U=>url,I=>input,E=>exception,L=>lang,A=>controller,R=>action
2、模版渲染:$this->display() => return view()/return $this->fetch();
3、在model中调用自身model:$this => Db::table($this->table)
4、在新建控制器与模型时的命名:
①控制器去掉后缀controller:UserController => User
②模型去掉后缀model:UserModel => User
5、url访问:
如果控制器名使用驼峰法,访问时需要将各字母之间用下划线链接后进行访问。
eg:控制器名为AddUser,访问是用add_user来进行访问
6、在TP5中支持配置二级参数(即二维数组),配置文件中,二级配置参数读取:
①Config::get('user.type');
②config('user.type');
7、模板中支持三元运算符的运算:{$info.status ? $info.msg : $info.error}还支持这种写法:
{$varname.aa ?? 'xxx'}或{$varname.aa ?: 'xxx'}
8、TP5内置标签:
系统内置的标签中,volist、switch、if、elseif、else、foreach、compare(包括所有的比较标签)、(not)present、(not)empty、(not)defined等
9、TP5数据验证:
$validate = new Validate(['name' => 'require|max:25','email' => 'email']);
$data = ['name' => 'thinkphp','email' => 'thinkphp@qq.com'];
if(!validate->check($data)){
debug::dump($validate->getError());
}
注:使用助手函数实例化验证器——$validate = validate(‘User’);
10、TP5实现了内置分页,使用如下:
查询状态为1的用户数据,且每页显示10条数据
$list = model('User')->where('status',1)->paginate(10);
$page = $this->render();
$this->assign('_list',$list);
$this->assign('_page',$page);
return $this->fetch();
模板文件中分页输出代码如下:
<div>{$_page}</div>
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
因为我一直都是用Mac,所以首选XAMPP,这个安转很简单安装好了之后直接操作这些就可以
关于XAMMP(。。)的安装,MyAdmin的使用,数据库的简单配置,或者Navicat的基本使用与操作等可以网站找到一大堆,或者你也可以不使用集成工具,自己配置一套,但是作为初学者跟人感觉没有必要,中间肯定会遇到什么问题的,搞不好你还要花一段时间专门解决这些问题,更夸张的是实在搞定不了还会让你放弃这条路,哈哈!
所以这里首选:XAMPP+Navicat
Yii分基础班和高级版,区别就是,高级版里面自带数据库及相关配置,分前后台,具体相关请查看官方介绍
解压-拷贝到-XAMPP的安装目录/htdocs文件夹里面(路径地址:/Applications/XAMPP/htdocs),然后在浏览器输入:http://127.0.0.1/basic/web/(这里是你前面配置完成的情况下),就会出现这个界面:
同基础版一样,拷贝到基础班的同级目录,然后在浏览器输入(这里分前后端)
分别就会出现一个网站的前后端。
Failed to create directory '/Applications/XAMPP/xamppfiles/htdocs/advanced/backend/runtime/logs': mkdir(): Permission denied
分析: 那是因为权限问题,类似Permission denied的应该都与权限有关
首先:基础版的时候出现类似Permission denied问题我使用类似下面的代码就可以解决问题。
chmod 777 对应的名字
chmod 777 *
但是在高级版的时候发现还是不行,网站狂搜一顿,发现需要使用超级管理员开启权限
sudo chmod -R 0777 /Applications/XAMPP/xamppfiles/htdocs/
输入密码就可以了
好了前戏就到这里,下面正式开始
注意
关于URL模式,文件夹含义,相关方法介绍,浏览器输入的形式等这里都不会做相关介绍,请查看官方文档,或者google。
在advance/backend/config里面的main-local.php文件中,有个$config中的components,里面已经有一个request,我们需要增加我们的数据库配置,后面插入如下代码(这里我的数据库密码是空的)。
'db'=>[
'class'=>'yii\db\Connection',
'dsn'=>'mysql:host=127.0.0.1;dbname=WWWiCocos',
'username'=>'root',
'password'=>'',
'charset'=>'utf8',
]
在advance/backend/controllers里面的SiteController中插入查询数据库的方法
public function actionGetList(){
$allTables = Yii::$app->db->createCommand("show tables")->queryAll();
print_r($allTables);exit;
}
这里需要注意的是,
输入:http://127.0.0.1/advanced/backend/web/index.php?r=site&a=get-list,既可以查看我数据库对应的表。
打开navicat,链接并打开WWWiCocos数据库,新建一张表,设置相关字段,然后保存为YiiDB
使用gii实现数据库模型文件的生成与CRUD
输入http://127.0.0.1/advanced/backend/web/index.php?r=gii
点击star,出现下面的界面,就可以开始做数据库的操作了。
开始Model Generator,去链接并生成数据库对应的表数据
填写相关信息,这里的namespace其实就是命名空间,会直接指定文件的路径,放在哪个文件夹,这里因为是数据库操作,不管前后端都会用到,所以我放在common里面的models中,
点击Preview,就可以预料生成数据库Model文件的路,并且提示你点击Generate去生成php文件
点击Generate就会生成文件成
然后我们回到PhpStorm,查看Common文件夹里面的models中就会多了一个文件叫iCocosYiiSearch.php
里面就会有我们所建数据库的信息
<?php
namespace common\models;
use Yii;
/**
* This is the model class for table "YiiDB".
*
* @property integer $id
* @property string $name
* @property integer $age
* @property string $sex
* @property string $love
*/
class iCocosYiiDBSearch extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'YiiDB';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['id', 'name', 'age', 'sex', 'love'], 'required'],
[['id', 'age'], 'integer'],
[['name'], 'string', 'max' => 20],
[['sex', 'love'], 'string', 'max' => 255]
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Name',
'age' => 'Age',
'sex' => 'Sex',
'love' => 'Love',
];
}
}
不信你可看看数据库中对应的字典和相关信息是否一样。
然后回到gii点击CRUD进行数据库增删查改对应php文件的生成
在界面填写相关信息,但是有两点需要注意,需要填写对应的路径,不能直接名字,因为我们生成的CRUD对应的php文件需要文件分层,还有一个需要注意的就是类的文件名首写字母必须大写。
点击PreView预览
点击Generate生成
回到PhpStorm查看对应的上面文件路径的文件生成就会一一对应
然后在浏览器输入下面的地址执行CocosdbController中对应的index方法就可以看到如下界面,
http://127.0.0.1/advanced/backend/web/index.php?r=cocosdb/index
开始使用gii创建用户数据,点击Genertor,生成之后可以对表数据进行相应的更改
回到表界面可以看到一条数据已经生成
然后就是使用代码来做你想做的事情了
这里我们做一些简单的界面处理
1.标题:backend/Views/cocosdb打开index.php(还有一个create.php),最上面一行
$this->title = 'iCocos用户管理';
2.字段相关提示:common/model打开iCocosYiiDBSearch.php,修改attributeLabels方法,这里改完之后里面CRUD也会改
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => '用户ID',
'name' => '用户昵称',
'age' => '用户年龄',
'sex' => '用户性别',
'love' => '用户兴趣',
];
}
3.backend/Views/cocosdb打开view.php中Div里面增加附加标签(这是在Yii有些功能满足不了我们的要求的时候使用,拓张更多想要的东西)。
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'name',
'age',
'sex',
'love',
['label'=>'附加信息','value'=>'<span onclick="fun()">附加字段</span>','format'=>'html'],
],
]) ?>
同时可以结合js是哪一些想要的效果。
好了,到这里就已经基本上结束了,在下一次,我将开始先在项目中新建一个文件夹API,专门用来实现接口,给前段,后端,移动端的调用。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
最近公司需求不多,正好研究一下 App 瘦身的办法,写了点小总结。
如果你不知道下面几个问题,不妨可以看看文章。
使用 .xcassets 有什么好处?
@1x 、@2x 和 @3x 会一起内置到安装包中吗?
PDF 和 @1x 、@2x 和 @3x 有什么区别?
如果我有一个 10 x 10 的控件和一个 50 x 50 的控件,美工需要制作几张 PDF?
Iconfont 是什么?PDF 和 Iconfont 有什么区别?
启动图的正确打开方式?
使用 Swift 或者 混编会增大多少的包体积?
Install Smallest or Coding Fastest ?
在瘦身之前,首先需要分析一下,我们可以从哪几个方面入手。(以 Yep 为例)
Yep是一款很优秀的 Swift 开源软件。
https://github.com/CatchChat/Yep
App 的瘦身主要是针对于安装包,而在 iOS 中安装包就是一个以 .ipa 结尾的压缩包。我们可以通过 iTunes 下载获取这个 .ipa 来分析。
稍微整理一下,大致可以分为以下几类。
资源层面:
Assets.car:项目中所有 .xcassets 的压缩包
image: 图片资源文件
Video && Audio :音频 或者 视频。
代码层面:
国际化:国际化适配的 String ===> 89K
Xib && Storyboard:Xib 和 Storyboard 编译后的文件。
Yep :项目可执行文件。
Frameworks:Embedded Frameworks,项目中使用的动态库
其他:
other:配置文件
PlugIns:YepShare,一个共享的插件。
虽然 Yep 不能代表所有的 App,但是在对于 Yep 的 ipa 分析之后,大致可以总结出,对一个 App 的安装包瘦身,可以从资源层面和代码层面两个层面入手。
在讲资源层面之前,希望我们能达到一个共识,那就是所谓的资源文件指的是 图片、视频、音频。
Remote : 将资源文件放在服务器上,当用户下载完 App 后根据需要再下载。
Local : 将资源文件集成到安装包中的。
对于 Remote 的方式,如果做好策略(比如缓存),那么理论上,我们可以把 非必须的资源文件 都放到服务器上,这样对资源压缩率达到了 100%。也就是说安装包 没有任何非必须资源文件 。
必须资源文件:例如应用图标、启动图的这种配置图片。
苹果的 On-Demand Resources(http://benbeng.leanote.com/post/On-Demand-Resources-Guide) 也是通过这种按需加载资源的思路给我们提供了一种阶段性加载资源的途径,具体的不展开描述,你可以点前面的链接进行查看。但是虽然以关卡、tag这种方式来按需加载资源,但是苹果的服务器对于中国用户来说实在是慢的不行,所以暂时不建议采取这种方式。你们可以在自己服务器上实现这种策略方式来加载图片。
当然全部将非必须资源文件放到服务器上明显是不现实的,对于一些必用资源文件,还是需要将资源文件 集成到安装包中的。
必用资源文件:安装了 App 肯定会用到。
Create group 和 Create folder references
这两种其实就是直接把资源文件 拖 进去,在 Xcode 打包之后,所有图片都在可执行文件的相同目录下面。这也是很多老的 App 或者目前部分 App 的使用方式。
.xcassets
这是苹果在 Xcode 5 出来之后,推荐我们使用的图片管理方式,提供了图片渲染、拉伸模式模式、机型适配等功能。在 Xcode 打包之后所有的.xcassets 文件都会放入一个Assets.car文件中。
一般 App 的图片内置方式
采用拖的方式,图片包含@1x、@2x 和 @3x。
采用拖的方式,图片只包含 @2x 和 @3x。
采用拖的方式,图片只包含 @2x 或 @3x。
采用.xcassets的方式,图片包含@1x、@2x 和 @3x。
采用.xcassets的方式,图片只包含@2x 和 @3x。
采用.xcassets的方式,图片只 包含@2x 或 @3x。
采用.xcassets的方式,图片使用 PDF。
(可能还有其他方式,希望你能告诉我。)
首先@1x、@2x 和 @3x主要是为了适配不同 ppi 的机子而做了一种策略。@1x主要是为了适配 iPhone 4 之前的 非 Retina 屏幕,@2x 主要是为了适配 非 plus的 iPhone 设备, @3x 是为了适配一个点的 3 * 3 个像素的手机。
因为 iPhone 4 之前的机子基本没有什么 App 会去适配,所以一般来说都会删除,所以就有了 『采用拖的方式,图片只包含@2x 和 @3x』 的方式。但是假如你只提供一张图片,例如你只提供了一张 @3x 的图片,iOS 系统在 iPhone 7 上无法找到 @2x 的图片,会去查找 @3x 或者 @1x 等,再根据实际分辨率进行拉伸,最后把像素铺到屏幕上。所以在能够接受查找和拉伸造成的性能消耗的前提下,我们可以只用一张通用的图片,所以就有了 『采用拖的方式,图片包含@2x 或 @3x』 的方式。
以一个 14M 的图片资源(包含@1x 、@2x、@3x)来说,如果所有的图片去除掉 @1x 能减少 1M 左右,去除掉@2x能去掉 4M 左右。因此采用『采用拖的方式,图片包含@2x 或 @3x』的方式虽然损失了一点性能,单大概图片资源大概减少了35%左右,。
我们都知道了,采用『采用拖的方式,图片包含@2x 或 @3x』的方式大概图片资源大概减少了35%左右,但是稍微损失了一点性能。有什么方式可以减少掉这点性能消耗呢?
“很幸运” ,苹果在 iOS 9 终于意识到了这个问题,然后提供了一个叫做 App Slicing(如下图所示)的东西。App Slicing大致就是App Store会根据不同的设备准备不同的安装包(App Variant),每个安装包(App Variant)都只有相应尺寸的图片,比如 iPhone 6 去下载时,只会下载到 @2x 的图片的安装包(App Variant)。但能实现这个功能的前提是图片需要放置在.xcassets去管理。
所以,目前许多 App 采用 『.xcassets的方式,图片只 包含@2x 或 @3x』 其实是没意义的,特别是在你不适配 iOS 8 的时候,你这么做是强行降低了 App 的性能。当然你要觉得为了 8% 的非 iOS 9 用户 减少 App 安装包大小 而去降低另外 92% 的用户的 App 运行性能 没什么问题,那么你可以采取上面这种方式。
我最早是在这一篇博客(http://martiancraft.com/blog/2014/09/vector-images-xcode6/)中看到的,当然 Yep 也是这种方式。大致是删掉 @2x 和 @3x 的图片,然后替换成 矢量图 PDF,最后放入.xcassets中去。
而 Xcode 在打包的过程中,根据你的矢量PDF图的大小,生成@1x、@2x和@3x的图。例如你的PDF图是4545px,那么Xcode会在编译时生成下面3个png:4545px 、9090px、135135px,最后再放入Assets.car中。所以采用@1x、@2x 和 @3x 和 PDF 两种方式本质上是一样的。
在这里有很多人会有一个误解,例如在 App 中有一个 10 10 pt 和 一个 50 50 pt 的 imageView 都用了一个相同的图标。很多人会以为做一个就够了,因为 pdf 是矢量图。但是其实是需要一个 10 10 px 和 50 50 px 的两张 pdf 才可以,因为只用一张的话,另外一张用的其实就是 10 * 10px 的 PDF 的产物。
我是用tinypng 来压缩的,应该是以最小的占用量达到了最适合的效果。但是其实.xcassets 也会为你做一部分的压缩。如下图所示:
.xcassets 的压缩应该还对图片进行了处理这也就是为什么 840KB 压缩了 81.5%,Assets.car却没有减少那么多。
同时也有人在试验中发现,用一些压缩工具似乎没有很么实际效果,这也有可能是因为 Xcode 在打包的时候做了一定的处理。
启动图在一个项目资源中占比其实蛮大的,之前见过一个项目 6 张启动图大概有5M 左右,最大的是2M。
iPad 2 and iPad mini (@1x): 768 x 1024
iPad and iPad mini (retina @2x): 1536 x 2048
iPhone 4s (retina @2x) 640 x 960
iPhone 5 (@2x): 640 x 1136
iPhone 6 (@2x): 750 x 1334
iPhone 6 Plus (@3x): 1242 x 2208
但是自从LaunchScreen.storyboard出来一后完全没必要做这么多张了。只需要将启动图设置为LaunchScreen.storyboard 然后在LaunchScreen.storyboard上设置一张 imageView 。最后再弄一张启动图的 pdf 就可以了。
首先这个东西估计很多人不知道,我也是在@桌同学的提醒下才知道原来 iOS 也是可以用 iconfont 的。最早这个东西是为 Web 设计的,主要是因为 网页的 大小直接影响了加载速度,所以在压缩上连小 icon 都不放过,当然还有一个最主要的目的就是减少请求次数,因为如果是图片的话,一个图片就是一次请求。
具体的效果可以看一下,使用IconFont减小iOS应用体积(http://johnwong.github.io/mobile/2015/04/03/using-icon-font-in-ios.html)这篇文章。
虽然看上去效果不多,但对于一些比较追求精致的公司可以尝试一下这种方式。
期中,PDF 和 iconfont 两个都是矢量的概念,但是 iconfont 在整个 App 中不管多少种尺寸只需要一个 iconf,但是 PDF 可能需要多个。
一些 APP 的一些功能可以用 HTML5 + WebView 的方式来实现。而 HTML 5 这个可以通过下面几种方式一步步优化:
让做前端的给一个最小的包内置到 App,去除无用代码、代码混淆压缩等。
将所有图片 Remote 化。
将所有页面 Remote 化。
当然,还要注意资源文件重复的问题。而资源文件重复问题主要有几种:名字相同、名字不同内容相同/相似。
对于名字相同的问题,你可以把原来的拖的方式改为.xcassets,他会自动管理相同名字的图片。然后把多余的去掉
名字不同内容相同/相似:你可以使用Duplicate Photos工具
还有一个问题就是资源文件没有用,却占了空间,可以使用LSUnusedResources将代码中没用到的文件删除。当然可能存在误删,比如用数组加载的图片,这个工具无法识别。
Install Smallest VS. Coding Fastest
虽然说我本人更喜欢用 Swift 来写 App。但从 App 瘦身的角度,不推荐使用 Swift,不论纯 Swift 还是 混编。原因很简单。看一下下面的图:
这是任何一个包含有 Swift 代码的 App 都有的一个为了支持 Swift 的动态库集合,在10M 左右。如果你使用 Objective – C 完全不用这个东西。
当然,我是可以接受安装包大10M 来用 Swift 写的。
这个问题也是我在分析 Yep 的第三方库的时候发现的问题,因为 Yep 使用的是 Realm,据说是目前是性能最好的移动端数据库。但是在三方库中可以看到,Realm 的支持占了很大的比重,大约在 8M 左右。但是如果使用 FMDB 话只需要192KB,而 CoreData 几乎可以忽略不计。下面是部分截图。
最开始是在Bang的这篇文章中看到用ARC比用 MRC 会导致可执行文件大10%。起初我是不相信的,但是在我用 SDWebImage 的1.0 版测试之后,ARC 比 MRC 的可执行程序增加了14% +。所以MRC 比 ARC 编译成可执行文件之后更小,具体的测试方法可以去他的博客看,这里就不重复了。
苹果官方文档 对二进制 __TEXT 段大小有限制:
代码实在瘦不下去怎么办?
利用 rename_section 过审核,在Xcode中向 “Other Linker Flags” 中添加
-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring
-Wl,-rename_section,__TEXT,__const,__RODATA,__const
-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname
-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname
-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype
Swift && Realm : 首先 Swift 是因为不稳定,所以支持的动态库没有集成到系统的”dyld的共享缓存”中去。而 Realm 因为不是苹果自己开发的,所以支持的动态库也没有集成到系统的”dyld的共享缓存”中去。所以都内置在了 App 中,而且这两个功能需要写很多代码来实现,因此容量又很大,导致看起来这两个东西占了很大的“无用”的容量。(ps.关于iOS 中库的问题,你可以去我的笔记中查看~)
ARC:因为 ARC 叫做自动引用计数,他的实现方式其实就是 Xcode 在编译的时候自动给你加内存管的代码,但是机器毕竟没人聪明,Xcode 会在很多情况下增加很多没用的代码,这也是为什么 ARC 的底层实现比 MRC 更快,但是实际运行性能上在有些时候却不及 MRC 的原因,而正因为增加了很多没用的代码,ARC 最终编译包会比 MRC 大。
总结前面的几个问题,归根结底于一个问题,那就是Install Smallest VS. Coding Fastest。很多时候为了追求更快的编码速度,总会有所损失,但是在我看来这些事值得的,不然为什么我们不用 C 来代替 objective-c 或者用汇编来代替 C 呢?
bitcode 是被编译程序的一种中间形式的代码。包含 bitcode 配置的程序将会在 App Store 上被编译和链接。 bitcode 允许苹果在后期重新优化我们程序的二进制文件,而不需要我们重新提交一个新的版本到 App Store 上。
当我们提交程序到 App Store上时, Xcode 会将程序编译为一个中间表现形式( bitcode )。然后 App store 会再将这个 bitcode 编译为可执行的64位或32位程序。
所以,通过这个方式,我们可以做到架构级别的App Slicing。
结合上面的内容,再加上Bang大神写的博客(http://blog.cnbang.net/tech/2544/%EF%BC%89%EF%BC%8C%E6%88%91%E6%80%BB%E7%BB%93%E4%BA%86%E5%87%A0%E6%9D%A1 Tips。排名越往前的我觉得越需要去优化。
Tip 1:去除重复、无用资源文件,解决名字重复问题。
Tip 2:图片使用.xcassets管理且无须考虑@1x\@2x\@3x 问题。万不得已再用拖的办法,同时结合一定策略方案进行包瘦身。
Tip 3:图片使用PDF 优先级高于 PNG,因为 Xcode 会帮你完成剩下的任务。
Tip 4:使用tinypng压缩PNG图片。视频可以通过 Final cut 等软件进行分辨率压缩。音频则降低码率即可。
Tip 5:icon 使用 iconfont
Tip 6:非必须资源文件可以放到自己服务器上, 但必用资源文件需要内置到安装包中。
Tip 7:HTML 5 需要将图片 Remote 化 或者将整个HTML 5 的页面 Remote化。
Tip 8:Build Settings->Optimization Leve release版应该选择Fastest, Smalllest
Tip 9:开启 BitCode
以下是几乎不可能去做的优化 Tips
Tip 10:尽可能的去除无用的代码、控制类名、方法名长度、冗余字符串
Tip 11:如果你想的话,不使用 Swift、不使用 Realm更甚至于尽量不使用 OC
Tip 12:MRC 比 ARC 编译成可执行文件之后更小。
更多:工作之余,写了点笔记,如果需要可以在我的 GitHub 看。
参考文章
App Thinning
http://t.cn/RVJ8kNd
Confirmed: Objective-C ARC is slow. Don’t use it! (sarcasm off)
http://t.cn/zYkzifW
4 XCODE ASSET CATALOG SECRETS YOU NEED TO KNOW
http://t.cn/RVJR2c0
使用IconFont减小iOS应用体积
http://t.cn/RVU7B3h
iOS可执行文件瘦身方法
http://t.cn/RZgnVL3
水平有限,若有错误,希望多多指正!coderonevv@gmail.com
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
之前没有整理过项目中遇到或者写过,或者经常要用的代码,可能觉得多写几遍就没事了,或者网上一找就有了。可是事实并非如果,首先,网上找的永远不是你的。其次,写得再多还是有粗心或者注意不到的地方。最后,整理成自己的能最快速度的找到并实现,提高效率。何乐而不为呢?
好了,废话不多说,理论也没有,大部分只要两个操作:copy-paste。有些还是需要做小小的改动的,根据项目需求。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat sectionHeaderHeight = 10; //这里是我的headerView和footerView的高度
if (_tableView.contentOffset.y<=sectionHeaderHeight&&_tableView.contentOffset.y>=0) {
_tableView.contentInset = UIEdgeInsetsMake(-_tableView.contentOffset.y, 0, 0, 0);
} else if (_tableView.contentOffset.y>=sectionHeaderHeight) {
_tableView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
}
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.tableView)
{
UITableView *tableview = (UITableView *)scrollView;
CGFloat sectionHeaderHeight = 64;
CGFloat sectionFooterHeight = 120;
CGFloat offsetY = tableview.contentOffset.y;
if (offsetY >= 0 && offsetY <= sectionHeaderHeight)
{
tableview.contentInset = UIEdgeInsetsMake(-offsetY, 0, -sectionFooterHeight, 0);
}else if (offsetY >= sectionHeaderHeight && offsetY <= tableview.contentSize.height - tableview.frame.size.height - sectionFooterHeight)
{
tableview.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, -sectionFooterHeight, 0);
}else if (offsetY >= tableview.contentSize.height - tableview.frame.size.height - sectionFooterHeight && offsetY <= tableview.contentSize.height - tableview.frame.size.height) {
tableview.contentInset = UIEdgeInsetsMake(-offsetY, 0, -(tableview.contentSize.height - tableview.frame.size.height - sectionFooterHeight), 0);
}
}
}
//第一种
srand((unsigned)time(0)); //不加这句每次产生的随机数不变
int i = rand() % 5;
//第二种
srandom(time(0));
int i = random() % 5;
//第三种
int i = arc4random() % 5 ;
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
CGRect frameH = self.tableView.tableHeaderView.frame;
frameH.size.height = 5;
UIView *headerView = [[UIView alloc] initWithFrame:frameH];
[self.tableView setTableHeaderView:headerView];
CGRect frameF = self.tableView.tableHeaderView.frame;
frameF.size.height = 1;
UIView *footerView = [[UIView alloc] initWithFrame:frameF];
[self.tableView setTableFooterView:footerView];
}
直接设置内边距,TableView会直接根据内边距进行相应的缩进!
CGSize itemSize = CGSizeMake(self.image.size.width, self.image.size.height);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *dynamicCellImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:model.cover]]];
UIGraphicsBeginImageContextWithOptions(itemSize, NO, [UIScreen mainScreen].scale);
//压缩图片
CGSize newSize;
CGImageRef imageRef = nil;
if ((dynamicCellImage.size.width / dynamicCellImage.size.height) < (self.image.size.width / self.image.size.height)) {
newSize.width = dynamicCellImage.size.width;
newSize.height = dynamicCellImage.size.width * self.image.size.height / self.image.size.width;
imageRef = CGImageCreateWithImageInRect([dynamicCellImage CGImage], CGRectMake(0, fabs(dynamicCellImage.size.height - newSize.height) / 2, newSize.width, newSize.height));
} else {
newSize.height = dynamicCellImage.size.height;
newSize.width = dynamicCellImage.size.height * self.image.size.width / self.image.size.height;
imageRef = CGImageCreateWithImageInRect([dynamicCellImage CGImage], CGRectMake(fabs(dynamicCellImage.size.width - newSize.width) / 2, 0, newSize.width, newSize.height));
}
dispatch_async(dispatch_get_main_queue(), ^{
self.image.image = [UIImage imageWithCGImage:imageRef];
});
UIGraphicsEndImageContext();
});
//加模糊效果,image是图片,blur是模糊度
+ (UIImage *)blurryImage:(UIImage *)image withBlurLevel:(CGFloat)blur {
//模糊度,
if ((blur < 0.1f) || (blur > 2.0f)) {
blur = 0.5f;
}
//boxSize必须大于0
int boxSize = (int)(blur * 100);
boxSize -= (boxSize % 2) + 1;
// iCocosLog(@"boxSize:%i",boxSize);
//图像处理
CGImageRef img = image.CGImage;
//图像缓存,输入缓存,输出缓存
vImage_Buffer inBuffer, outBuffer;
vImage_Error error;
//像素缓存
void *pixelBuffer;
//数据源提供者,Defines an opaque type that supplies Quartz with data.
CGDataProviderRef inProvider = CGImageGetDataProvider(img);
// provider’s data.
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
//宽,高,字节/行,data
inBuffer.width = CGImageGetWidth(img);
inBuffer.height = CGImageGetHeight(img);
inBuffer.rowBytes = CGImageGetBytesPerRow(img);
inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
//像数缓存,字节行*图片高
pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
outBuffer.data = pixelBuffer;
outBuffer.width = CGImageGetWidth(img);
outBuffer.height = CGImageGetHeight(img);
outBuffer.rowBytes = CGImageGetBytesPerRow(img);
// 第三个中间的缓存区,抗锯齿的效果
void *pixelBuffer2 = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
vImage_Buffer outBuffer2;
outBuffer2.data = pixelBuffer2;
outBuffer2.width = CGImageGetWidth(img);
outBuffer2.height = CGImageGetHeight(img);
outBuffer2.rowBytes = CGImageGetBytesPerRow(img);
//Convolves a region of interest within an ARGB8888 source image by an implicit M x N kernel that has the effect of a box filter.
error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer2, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
error = vImageBoxConvolve_ARGB8888(&outBuffer2, &inBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
if (error) {
iCocosLog(@"error from convolution %ld", error);
}
// iCocosLog(@"字节组成部分:%zu",CGImageGetBitsPerComponent(img));
//颜色空间DeviceRGB
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//用图片创建上下文,CGImageGetBitsPerComponent(img),7,8
CGContextRef ctx = CGBitmapContextCreate(
outBuffer.data,
outBuffer.width,
outBuffer.height,
8,
outBuffer.rowBytes,
colorSpace,
CGImageGetBitmapInfo(image.CGImage));
//根据上下文,处理过的图片,重新组件
CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
UIImage *returnImage = [UIImage imageWithCGImage:imageRef];
//clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(pixelBuffer);
free(pixelBuffer2);
CFRelease(inBitmapData);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imageRef);
return returnImage;
}
/**
* 通常用于删除缓存的时,计算缓存大小
*/
//单个文件的大小
+ (long long) fileSizeAtPath:(NSString*) filePath{
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:filePath]){
return [[manager attributesOfItemAtPath:filePath error:nil] fileSize];
}
return 0;
}
/**
* 手机号判断
*
* @param mobileNum 号码字符串
*
* @return BOOL
*/
+ (BOOL)isMobileNumber:(NSString *)mobileNum
{
/**
* 移动号段正则表达式
*/
NSString *CM_NUM = @"^((13[4-9])|(147)|(15[0-2,7-9])|(178)|(18[2-4,7-8]))\\d{8}|(1705)\\d{7}$";
/**
* 联通号段正则表达式
*/
NSString *CU_NUM = @"^((13[0-2])|(145)|(15[5-6])|(176)|(18[5,6]))\\d{8}|(1709)\\d{7}$";
/**
* 电信号段正则表达式
*/
NSString *CT_NUM = @"^((133)|(153)|(177)|(18[0,1,9]))\\d{8}$";
NSPredicate *pred1 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM_NUM];
BOOL isMatch1 = [pred1 evaluateWithObject:mobileNum];
NSPredicate *pred2 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU_NUM];
BOOL isMatch2 = [pred2 evaluateWithObject:mobileNum];
NSPredicate *pred3 = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT_NUM];
BOOL isMatch3 = [pred3 evaluateWithObject:mobileNum];
if (isMatch1 || isMatch2 || isMatch3) {
return YES;
}else{
return NO;
}
}
-(BOOL)validateEmail:(NSString*)email
{
if((0 != [email rangeOfString:@"@"].length) &&
(0 != [email rangeOfString:@"."].length))
{
NSCharacterSet* tmpInvalidCharSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSMutableCharacterSet* tmpInvalidMutableCharSet = [[tmpInvalidCharSet mutableCopy] autorelease];
[tmpInvalidMutableCharSet removeCharactersInString:@"_-"];
NSRange range1 = [email rangeOfString:@"@"
options:NSCaseInsensitiveSearch];
//取得用户名部分
NSString* userNameString = [email substringToIndex:range1.location];
NSArray* userNameArray = [userNameString componentsSeparatedByString:@"."];
for(NSString* string in userNameArray)
{
NSRange rangeOfInavlidChars = [string rangeOfCharacterFromSet: tmpInvalidMutableCharSet];
if(rangeOfInavlidChars.length != 0 || [string isEqualToString:@""])
return NO;
}
//取得域名部分
NSString *domainString = [email substringFromIndex:range1.location+1];
NSArray *domainArray = [domainString componentsSeparatedByString:@"."];
for(NSString *string in domainArray)
{
NSRange rangeOfInavlidChars=[string rangeOfCharacterFromSet:tmpInvalidMutableCharSet];
if(rangeOfInavlidChars.length !=0 || [string isEqualToString:@""])
return NO;
}
return YES;
}
else {
return NO;
}
}
-(BOOL)isValidateEmail:(NSString *)email {
NSString *emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
return [emailTest evaluateWithObject:email];
}
- (NSURL *)smartURLForString:(NSString *)str
{
NSURL * result;
NSString * trimmedStr;
NSRange schemeMarkerRange;
NSString * scheme;
assert(str != nil);
result = nil;
trimmedStr = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ( (trimmedStr != nil) && (trimmedStr.length != 0) ) {
schemeMarkerRange = [trimmedStr rangeOfString:@"://"];
if (schemeMarkerRange.location == NSNotFound) {
result = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", trimmedStr]];
} else {
scheme = [trimmedStr substringWithRange:NSMakeRange(0, schemeMarkerRange.location)];
assert(scheme != nil);
if ( ([scheme compare:@"http" options:NSCaseInsensitiveSearch] == NSOrderedSame)
|| ([scheme compare:@"https" options:NSCaseInsensitiveSearch] == NSOrderedSame) ) {
result = [NSURL URLWithString:trimmedStr];
} else {
// It looks like this is some unsupported URL scheme.
}
}
}
return result;
}
//判断
-(void) validateUrl: (NSURL *) candidate {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:candidate];
[request setHTTPMethod:@"HEAD"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"error %@",error);
if (error) {
NSLog(@"不可用");
}else{
NSLog(@"可用");
}
}];
[task resume];
}
/*!
* @brief 把格式化的JSON格式的字符串转换成字典
* @param jsonString JSON格式的字符串
* @return 返回字典
*/
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
if (jsonString == nil) {
return nil;
}
iCocosLog(@"%@", jsonString);
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if(err) {
iCocosLog(@"json解析失败:%@",err);
return nil;
}
return dic;
}
NSArray *uids = [self.allModelUID objectAtIndexCheck:range];
NSError *error = nil;
NSData *picsJsonData = [NSJSONSerialization dataWithJSONObject:uids
options:NSJSONWritingPrettyPrinted
error:&error];
NSString *JSONString = [[NSString alloc] initWithData:picsJsonData encoding:NSUTF8StringEncoding];
typedef NS_ENUM(char, iPhoneModel){//0~3
iPhone4,//320*480
iPhone5,//320*568
iPhone6,//375*667
iPhone6Plus,//414*736
UnKnown
};
/**
* return current running iPhone model
*
* @return iPhone model
*/
+ (iPhoneModel)iPhonesModel {
//bounds method gets the points not the pixels!!!
CGRect rect = [[UIScreen mainScreen] bounds];
CGFloat width = rect.size.width;
CGFloat height = rect.size.height;
//get current interface Orientation
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
//unknown
if (UIInterfaceOrientationUnknown == orientation) {
return UnKnown;
}
/**
// portrait width * height
// iPhone4:320*480
// iPhone5:320*568
// iPhone6:375*667
// iPhone6Plus:414*736
*/
//portrait
if (UIInterfaceOrientationPortrait == orientation) {
if (width == 320.0f) {
if (height == 480.0f) {
return iPhone4;
} else {
return iPhone5;
}
} else if (width == 375.0f) {
return iPhone6;
} else if (width == 414.0f) {
return iPhone6Plus;
}
} else if (UIInterfaceOrientationLandscapeLeft == orientation || UIInterfaceOrientationLandscapeRight == orientation) {//landscape
if (height == 320.0) {
if (width == 480.0f) {
return iPhone4;
} else {
return iPhone5;
}
} else if (height == 375.0f) {
return iPhone6;
} else if (height == 414.0f) {
return iPhone6Plus;
}
}
return UnKnown;
}
//获取当前系统版本
#define __ios10_0__ ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0)
#define __ios9_0__ ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0)
#define __ios8_0__ ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
// 日志输出
#ifdef DEBUG // 开发阶段-DEBUG阶段:使用Log
#define iCocosLog(...) NSLog(__VA_ARGS__)
#else // 发布阶段-上线阶段:移除Log
#define iCocosLog(...)
#endif
详细
#ifdef DEBUG
#define iCocosLog(format, ...) printf("\n[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define iCocosLog(format, ...)
#endif
// 颜色
#define iCocosARGBColor(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:a]
#define iCocosColor(r, g, b) iCocosARGBColor((r), (g), (b), 255)
#define random(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)/255.0]
#define iCocosRandomColor (random(arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256), arc4random_uniform(256)))
// 弱引用
#define iCocosWeakSelf __weak typeof(self) weakSelf = self;
// 屏幕尺寸
#define iCocosScreenH [UIScreen mainScreen].bounds.size.height
#define iCocosScreenW [UIScreen mainScreen].bounds.size.width
/** 获取当前View的控制器对象 */
-(UIViewController *)getCurrentViewController{
UIResponder *next = [self nextResponder];
do {
if ([next isKindOfClass:[UIViewController class]]) {
return (UIViewController *)next;
}
next = [next nextResponder];
} while (next != nil);
return nil;
}
/*!
@method objectAtIndexCheck:
@abstract 检查是否越界和NSNull如果是返回nil
@result 返回对象
*/
- (id)objectStringForKey:(NSString *)key
{
if ([self objectForKey:key] == nil) {
// iCocosLog(@"键值对不存在");
return nil;
}
id value = [self objectForKey:key];
return value;
}
/*!
@method objectAtIndexCheck:
@abstract 检查是否越界和NSNull如果是返回nil
@result 返回对象
*/
- (id)objectStringForKey:(NSString *)key
{
if ([self objectForKey:key] == nil) {
// iCocosLog(@"键值对不存在");
return nil;
// return 0;
}
id value = [self objectForKey:key];
return value;
}
/*!
@method objectAtIndexCheck:
@abstract 检查是否越界和NSNull如果是返回nil
@result 返回对象
*/
- (id)objectAtIndexCheck:(NSUInteger)index {
if (index >= [self count]) {
iCocosLog(@"数组越界");
return nil;
}
id value = [self objectAtIndex:index];
if (value == [NSNull null]) {
iCocosLog(@"数组为空");
return nil;
}
return value;
}
/*!
@method objectAtIndexCheck:
@abstract 检查是否越界和NSNull如果是返回nil
@result 返回对象
*/
- (id)objectAtIndexCheck:(NSUInteger)index {
if (index >= [self count]) {
iCocosLog(@"数组越界");
return nil;
}
id value = [self objectAtIndex:index];
if (value == [NSNull null]) {
iCocosLog(@"数组为空");
return nil;
}
return value;
}
- (void)removeObjectAtCheckIndex:(NSInteger)index
{
if (index >= [self count]) {
iCocosLog(@"数组越界");
return ;
}
id value = [self objectAtIndex:index];
if (value == [NSNull null]) {
iCocosLog(@"数组为空");
return ;
}
[self removeObjectAtIndex:index];
}
- (void)shake {
CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
keyFrame.duration = 0.3;
CGFloat x = self.layer.position.x;
keyFrame.values = @[@(x - 30), @(x - 30), @(x + 20), @(x - 20), @(x + 10), @(x - 10), @(x + 5), @(x - 5)];
[self.layer addAnimation:keyFrame forKey:@"shake"];
}
+ (NSString *)nowTimes{
NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
int a=(int)([dat timeIntervalSince1970] + 0.5);
NSString *timeString = [NSString stringWithFormat:@"%d", a];//转为字符型
return timeString;
}
/*
* 当前程序的版本号
*/
-(NSString *)version{
//系统直接读取的版本号
NSString *versionValueStringForSystemNow=[[NSBundle mainBundle].infoDictionary valueForKey:(NSString *)kCFBundleVersionKey];
return versionValueStringForSystemNow;
}
- (void)showBadgeOnItemIndex:(int)index{
//移除之前的小红点
[self removeBadgeOnItemIndex:index];
//新建小红点
UIView *badgeView = [[UIView alloc]init];
badgeView.tag = 888 + index;
badgeView.backgroundColor = [UIColor redColor];
CGRect tabFrame = self.frame;
//确定小红点的位置
float percentX = (index +0.6) / TabbarItemNums;
CGFloat x = ceilf(percentX * tabFrame.size.width);
CGFloat y = ceilf(0.1 * tabFrame.size.height);
badgeView.frame = CGRectMake(x, y, 8, 8);
badgeView.layer.cornerRadius = badgeView.frame.size.width/2;
[self addSubview:badgeView];
}
- (void)hideBadgeOnItemIndex:(int)index{
//移除小红点
[self removeBadgeOnItemIndex:index];
}
- (void)removeBadgeOnItemIndex:(int)index{
//按照tag值进行移除
for (UIView *subView in self.subviews) {
if (subView.tag == 888+index) {
[subView removeFromSuperview];
}
}
}
@implementation UIView(Log)
+ (NSString *)searchAllSubviews:(UIView *)superview
{
NSMutableString *xml = [NSMutableString string];
NSString *class = NSStringFromClass(superview.class);
class = [class stringByReplacingOccurrencesOfString:@"_" withString:@""];
[xml appendFormat:@"<%@ frame=\"%@\">\n", class, NSStringFromCGRect(superview.frame)];
for (UIView *childView in superview.subviews) {
NSString *subviewXml = [self searchAllSubviews:childView];
[xml appendString:subviewXml];
}
[xml appendFormat:@"</%@>\n", class];
return xml;
}
- (NSString *)description
{
return [UIView searchAllSubviews:self];
}
@end
@implementation NSDictionary (Log)
- (NSString *)descriptionWithLocale:(id)locale
{
NSMutableString *str = [NSMutableString string];
[str appendString:@"{\n"];
// 遍历字典的所有键值对
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[str appendFormat:@"\t%@ = %@,\n", key, obj];
}];
[str appendString:@"}"];
// 查出最后一个,的范围
NSRange range = [str rangeOfString:@"," options:NSBackwardsSearch];
if (range.length) {
// 删掉最后一个,
[str deleteCharactersInRange:range];
}
return str;
}
@end
@implementation NSArray (Log)
- (NSString *)descriptionWithLocale:(id)locale
{
NSMutableString *str = [NSMutableString string];
[str appendString:@"[\n"];
// 遍历数组的所有元素
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[str appendFormat:@"%@,\n", obj];
}];
[str appendString:@"]"];
// 查出最后一个,的范围
NSRange range = [str rangeOfString:@"," options:NSBackwardsSearch];
if (range.length) {
// 删掉最后一个,
[str deleteCharactersInRange:range];
}
return str;
}
@end
//16位MD5加密方式
- (NSString *)getMd5_16Bit_String:(NSString *)srcString{
//提取32位MD5散列的中间16位
NSString *md5_32Bit_String=[self getMd5_32Bit_String:srcString];
NSString *result = [[md5_32Bit_String substringToIndex:24] substringFromIndex:8];//即9~25位
return result;
}
//32位MD5加密方式
- (NSString *)getMd5_32Bit_String:(NSString *)srcString{
const char *cStr = [srcString UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, strlen(cStr), digest );
NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[result appendFormat:@"%02x", digest[i]];
return result;
}
/**
* 使用背景颜色设置按钮不同状态的图片
*
* @param color 颜色
*
* @return 背景图片
*/
+ (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// 判断对象是否为空
- (BOOL)isBlanceObject:(id)object{
if (object == nil || object == NULL) {
return YES;
}
if ([object isKindOfClass:[NSNull class]]) {
return YES;
}
return NO;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
// 获取通知的信息字典
NSDictionary *userInfo = [notification userInfo];
// 获取键盘弹出后的rect
NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = [aValue CGRectValue];
// 获取键盘弹出动画时间
NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration;
[animationDurationValue getValue:&animationDuration];
}
- (void)keyboardWillHide:(NSNotification *)notification {
// 获取通知信息字典
NSDictionary* userInfo = [notification userInfo];
// 获取键盘隐藏动画时间
NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSTimeInterval animationDuration;
[animationDurationValue getValue:&animationDuration];
}
-(NSString )getUniqueDeviceIdentifierAsString { NSString appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *strApplicationUUID = [SAMKeychain passwordForService:appName account:@"incoding"];
if (strApplicationUUID == nil)
{
strApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSError *error = nil;
SAMKeychainQuery *query = [[SAMKeychainQuery alloc] init];
query.service = appName;
query.account = @"incoding";
query.password = strApplicationUUID;
query.synchronizationMode = SAMKeychainQuerySynchronizationModeNo;
[query save:&error];
}
return strApplicationUUID;
}
- (void)movFileTransformToMP4WithSourceUrl:(NSURL *)sourceUrl completion:(void(^)(NSString *Mp4FilePath))comepleteBlock
{
/**
* mov格式转mp4格式
*/
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:sourceUrl options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
NSLog(@"%@",compatiblePresets);
if ([compatiblePresets containsObject:AVAssetExportPresetHighestQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality];
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMddHHmmss"];
NSString *uniqueName = [NSString stringWithFormat:@"%@.mp4",[formatter stringFromDate:date]];
NSString * resultPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:uniqueName];//PATH_OF_DOCUMENT为documents路径
NSLog(@"output File Path : %@",resultPath);
exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
exportSession.outputFileType = AVFileTypeMPEG4;//可以配置多种输出文件格式
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^(void)
{
switch (exportSession.status) {
case AVAssetExportSessionStatusUnknown:
break;
case AVAssetExportSessionStatusWaiting:
break;
case AVAssetExportSessionStatusExporting:
break;
case AVAssetExportSessionStatusCompleted:
{
comepleteBlock(resultPath);
NSLog(@"mp4 file size:%lf MB",[NSData dataWithContentsOfURL:exportSession.outputURL].length/1024.f/1024.f);
}
break;
case AVAssetExportSessionStatusFailed:
break;
case AVAssetExportSessionStatusCancelled:
break;
}
}];
}
}
+ (void)uploadImage:(UIImage *)imageIcon successUpload:(void (^)(id responseObject))successUpload failureUpload:(void (^)(NSError *error))failureUpload;
{
// 拿到文件
NSString *NSDocmentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *iconPath = [NSDocmentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"faceUrl.png"]];
//NSURL *url = [NSURL fileURLWithPath:iconPath];
long long size = [iCocosGetSize fileSizeAtPath:iconPath];
if (size >= 7000000) {
[SVProgressHUD showInfoWithStatus:@"图片过大,请重新上传 \n 请不要上传超过7Mb文件"];
NSDictionary *errDict = [NSDictionary dictionaryWithObject:@"big" forKey:@"state"];
failureUpload((NSError *)errDict);
return;
}
//1:文件的32位MD5值
NSString *strForEight = [iCocosFormatFileGetEight getStringWithEight:iconPath];
//2:文件的前8个字节的16位+文件的后8个字节的16位
NSString *str32MD5 = [NSString getMd5_32Bit_String:iconPath];
NSString *str64 = [NSString stringWithFormat:@"%@%@", str32MD5,strForEight];
//存图片
// NSData *imageData = UIImageJPEGRepresentation(imageIcon, 1.0);//将UIImage转为NSData,1.0表示不压缩图片质量。
NSData *imageData = [iCocosFileCondenseTools resetSizeOfImageData:imageIcon maxSize:50];
[imageData writeToFile:iconPath atomically:YES];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
// NSString *urlStrIF = [NSString stringWithFormat:@"%@file/exist%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl, [iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//文件的32位MD5+前8个字节的16位+后8个字节的16位
dict[@"file_md5"] = str64;
dict[@"is_blur"] = @(1);
dict[@"file_size"] = @([iCocosGetSize fileSizeAtPath:iconPath]);
dict[@"ext"] = @"png";
/**
* 超时时间
*/
[manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
manager.requestSerializer.timeoutInterval = 10.f;
[manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
// [manager POST:urlStrIF parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[iCocosAFNPOSTRequestData iCocos_POST_HostSecurity:@"file/exist" hostHeaderValue:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileHost firstRequestWithUrl:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl secondRequestWithIp:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileIp params:dict success:^(id response) {
NSString *state = [NSString stringWithFormat:@"%@", [response objectStringForKey:@"state"]];
NSString *msg= [NSString stringWithFormat:@"%@", [response objectStringForKey:@"msg"]];
if ([state isEqualToString:@"0"]) {
NSString *exist = [NSString stringWithFormat:@"%@", [[response objectStringForKey:@"data"] objectStringForKey:@"exist"]];
/**
* 注意这里需要换成真实服务器地址
*/
if ([exist isEqualToString:@"0"]) { //不存在就需要发送请求
NSString *imageUrl = [NSString stringWithFormat:@"%@file/up%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl,[iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"blur"] = @(1);
AFHTTPSessionManager *mger = [AFHTTPSessionManager manager];
AFJSONResponseSerializer *response = [AFJSONResponseSerializer serializer];
response.removesKeysWithNullValues = YES;
manager.responseSerializer = response;
manager.requestSerializer = [AFHTTPRequestSerializer serializer];//响应
mger.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"image/png", @"text/html", nil];
/**
* 超时时间
*/
[manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
manager.requestSerializer.timeoutInterval = 10.f;
[manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
[mger POST:imageUrl parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// 上传文件
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.png", str];
[formData appendPartWithFileData:imageData name:@"file" fileName:fileName mimeType:@"image/png"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
iCocosLog(@"封面图片================%@", uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSDictionary *dataDic = [responseObject objectStringForKey:@"data"];
successUpload(dataDic);
iCocosLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
iCocosLog(@"上传错误:%@", error);
failureUpload(error);
}];
} else {
NSDictionary *dataDic = [response objectStringForKey:@"data"];
successUpload(dataDic);
}
} else {
successUpload(response);
}
} failure:^(NSError *error) {
failureUpload(error);
}];
}
+ (void)updateMOVVideo:(NSURL *)url successUpload:(void (^)(id responseObject))successUpload failureUpload:(void (^)(NSError *error))failureUpload;
{
//保存数据
// NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
// NSURL *url = [defaults URLForKey:@"RecordVideoUrl"];
NSData *videoData = [NSData dataWithContentsOfURL:url];
// NSString *videoUrl = [iCocosUpLoadVideoTools upLoadVideoGetVideoUrlWithFileUrlInSandbox:url];
// NSString *strUrl = [NSString stringWithContentsOfURL:url usedEncoding:0 error:nil];
// //1:文件的32位MD5值
// NSString *strForEight = [iCocosFormatFileGetEight getStringWithEight:strUrl];
//
// //2:文件的前8个字节的16位+文件的后8个字节的16位
// NSString *str32MD5 = [NSString getMd5_32Bit_String:strUrl];
NSString *str32MD5 = [iCocosRandomSix getSixRandom];
NSString *str64 = [NSString stringWithFormat:@"%@%@", str32MD5,str32MD5];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
// NSString *urlStrIF = [NSString stringWithFormat:@"%@file/exist%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl, [iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"file_md5"] = str64;
dict[@"is_blur"] = 0;
dict[@"file_size"] = @([iCocosGetSize fileSizeAtPath:[url absoluteString]]);
dict[@"ext"] = @"MOV";
/**
* 超时时间
*/
[manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
manager.requestSerializer.timeoutInterval = 10.f;
[manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
/** 获取视频是否上传 */
// [manager POST:urlStrIF parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[iCocosAFNPOSTRequestData iCocos_POST_HostSecurity:@"file/exist" hostHeaderValue:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileHost firstRequestWithUrl:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl secondRequestWithIp:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileIp params:dict success:^(id response) {
NSString *state = [NSString stringWithFormat:@"%@", [response objectStringForKey:@"state"]];
if ([state isEqualToString:@"0"]) {
NSString *exist = [response objectStringForKey:@"exist"];
/**
* 注意这里需要换成真实服务器地址
*/
if (exist == 0) { //不存在就需要发送请求
NSString *vidUrl = [NSString stringWithFormat:@"%@file/up%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl, [iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
// params[@"name:file"] = @""; //Content-Disposition: form-data; name="file"; filename="1.txt"
params[@"is_blur"] = @0;
params[@"need_mp4"] = @1;
AFHTTPSessionManager *mger = [AFHTTPSessionManager manager];
AFJSONResponseSerializer *response = [AFJSONResponseSerializer serializer];
response.removesKeysWithNullValues = YES;
manager.responseSerializer = response;
manager.requestSerializer = [AFHTTPRequestSerializer serializer];//响应
[mger.securityPolicy setAllowInvalidCertificates:YES];
/** 上传视频 */
[mger POST:vidUrl parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// 上传文件
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.mov", str];
if (videoData != nil) {
[formData appendPartWithFileData:videoData name:@"file" fileName:fileName mimeType:@"video/quicktime"];
} else {
}
} progress:^(NSProgress * _Nonnull uploadProgress) {
// iCocosLog(@"%@", uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *state = [NSString stringWithFormat:@"%@", [responseObject objectStringForKey:@"state"]];
if ([state isEqualToString:@"0"]) {
NSDictionary *dataDic = [responseObject objectStringForKey:@"data"];
successUpload(dataDic);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
iCocosLog(@"上传错误:%@", error);
failureUpload(error);
}];
} else {
/**
* 已经上传
*/
NSDictionary *dataDic = [response objectStringForKey:@"data"];
NSString *file_url = [NSString stringWithFormat:@"%@", [dataDic objectStringForKey:@"file_url"]];
NSString *mp4_file_url = [NSString stringWithFormat:@"%@", [dataDic objectStringForKey:@"mp4_file_url"]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:file_url forKey:@"video_url"];
[defaults setValue:mp4_file_url forKey:@"mp4_file_url"];
[defaults synchronize];
successUpload(dataDic);
}
} else {
successUpload(response);
}
} failure:^(NSError *error) { //上传错误
failureUpload(error);
}];
}
+ (void)updateMP4Video:(NSURL *)url successUpload:(void (^)(id responseObject))successUpload failureUpload:(void (^)(NSError *error))failureUpload
{
//保存数据
// NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
// NSURL *url = [defaults URLForKey:@"RecordVideoUrl"];
NSData *videoData = [NSData dataWithContentsOfURL:url];
// NSString *videoUrl = [iCocosUpLoadVideoTools upLoadVideoGetVideoUrlWithFileUrlInSandbox:url];
// NSString *strUrl = [NSString stringWithContentsOfURL:url usedEncoding:0 error:nil];
// //1:文件的32位MD5值
// NSString *strForEight = [iCocosFormatFileGetEight getStringWithEight:strUrl];
//
// //2:文件的前8个字节的16位+文件的后8个字节的16位
// NSString *str32MD5 = [NSString getMd5_32Bit_String:strUrl];
NSString *str32MD5 = [iCocosRandomSix getSixRandom];
NSString *str64 = [NSString stringWithFormat:@"%@%@", str32MD5,str32MD5];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
// NSString *urlStrIF = [NSString stringWithFormat:@"%@file/exist%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl, [iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"file_md5"] = str64;
dict[@"is_blur"] = 0;
dict[@"file_size"] = @([iCocosGetSize fileSizeAtPath:[url absoluteString]]);
dict[@"ext"] = @"mp4";
/**
* 超时时间
*/
[manager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
manager.requestSerializer.timeoutInterval = 10.f;
[manager.requestSerializer didChangeValueForKey:@"timeoutInterval"];
/** 获取视频是否上传 */
// [manager POST:urlStrIF parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[iCocosAFNPOSTRequestData iCocos_POST_HostSecurity:@"file/exist" hostHeaderValue:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileHost firstRequestWithUrl:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl secondRequestWithIp:[iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileIp params:dict success:^(id response) {
NSString *state = [NSString stringWithFormat:@"%@", [response objectStringForKey:@"state"]];
if ([state isEqualToString:@"0"]) {
NSString *exist = [response objectStringForKey:@"exist"];
/**
* 注意这里需要换成真实服务器地址
*/
if (exist == 0) { //不存在就需要发送请求
NSString *vidUrl = [NSString stringWithFormat:@"%@file/up%@", [iCocosUrlOperationTools shareiCocosUrlOperationTools].iCocosFileUrl, [iCocosURLRequestExtension getURLRequestExtension]];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
// params[@"name:file"] = @""; //Content-Disposition: form-data; name="file"; filename="1.txt"
params[@"is_blur"] = @0;
params[@"need_mp4"] = @1;
AFHTTPSessionManager *mger = [AFHTTPSessionManager manager];
AFJSONResponseSerializer *response = [AFJSONResponseSerializer serializer];
response.removesKeysWithNullValues = YES;
manager.responseSerializer = response;
manager.requestSerializer = [AFHTTPRequestSerializer serializer];//响应
[mger.securityPolicy setAllowInvalidCertificates:YES];
/** 上传视频 */
[mger POST:vidUrl parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// 上传文件
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.mp4", str];
if (videoData != nil) {
[formData appendPartWithFileData:videoData name:@"file" fileName:fileName mimeType:@"video/mp4"];
} else {
}
} progress:^(NSProgress * _Nonnull uploadProgress) {
// iCocosLog(@"%@", uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *state = [NSString stringWithFormat:@"%@", [responseObject objectStringForKey:@"state"]];
if ([state isEqualToString:@"0"]) {
NSDictionary *dataDic = [responseObject objectStringForKey:@"data"];
successUpload(dataDic);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
iCocosLog(@"上传错误:%@", error);
failureUpload(error);
}];
} else {
/**
* 已经上传
*/
NSDictionary *dataDic = [response objectStringForKey:@"data"];
NSString *file_url = [NSString stringWithFormat:@"%@", [dataDic objectStringForKey:@"file_url"]];
NSString *mp4_file_url = [NSString stringWithFormat:@"%@", [dataDic objectStringForKey:@"mp4_file_url"]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:file_url forKey:@"video_url"];
[defaults setValue:mp4_file_url forKey:@"mp4_file_url"];
[defaults synchronize];
successUpload(dataDic);
}
} else {
successUpload(response);
}
} failure:^(NSError *error) { //上传错误
failureUpload(error);
}];
}
同步获取中间帧,需要指定哪个时间点的帧,当获取到以后,返回来的图片对象是CFRetained过的,需要外面手动CGImageRelease一下,释放内存。通过AVAsset来访问具体的视频资源,然后通过AVAssetImageGenerator图片生成器来生成某个帧图片: // Get the video’s center frame as video poster image - (UIImage )frameImageFromVideoURL:(NSURL )videoURL { // result UIImage *image = nil;
// AVAssetImageGenerator
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
// calculate the midpoint time of video
Float64 duration = CMTimeGetSeconds([asset duration]);
// 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
// 通常来说,600是一个常用的公共参数,苹果有说明:
// 24 frames per second (fps) for film, 30 fps for NTSC (used for TV in North America and
// Japan), and 25 fps for PAL (used for TV in Europe).
// Using a timescale of 600, you can exactly represent any number of frames in these systems
CMTime midpoint = CMTimeMakeWithSeconds(duration / 2.0, 600);
// get the image from
NSError *error = nil;
CMTime actualTime;
// Returns a CFRetained CGImageRef for an asset at or near the specified time.
// So we should mannully release it
CGImageRef centerFrameImage = [imageGenerator copyCGImageAtTime:midpoint
actualTime:&actualTime
error:&error];
if (centerFrameImage != NULL) {
image = [[UIImage alloc] initWithCGImage:centerFrameImage];
// Release the CFRetained image
CGImageRelease(centerFrameImage);
}
return image;
}
异步获取某个帧的图片,与同步相比,只是调用API不同,可以传多个时间点,然后计算出实际的时间并返回图片,但是返回的图片不需要我们手动再release了。有可能取不到图片,所以还需要判断是否是AVAssetImageGeneratorSucceeded,是才转换图片:
// 异步获取帧图片,可以一次获取多帧图片
- (void)centerFrameImageWithVideoURL:(NSURL *)videoURL completion:(void (^)(UIImage *image))completion {
// AVAssetImageGenerator
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
// calculate the midpoint time of video
Float64 duration = CMTimeGetSeconds([asset duration]);
// 取某个帧的时间,参数一表示哪个时间(秒),参数二表示每秒多少帧
// 通常来说,600是一个常用的公共参数,苹果有说明:
// 24 frames per second (fps) for film, 30 fps for NTSC (used for TV in North America and
// Japan), and 25 fps for PAL (used for TV in Europe).
// Using a timescale of 600, you can exactly represent any number of frames in these systems
CMTime midpoint = CMTimeMakeWithSeconds(duration / 2.0, 600);
// 异步获取多帧图片
NSValue *midTime = [NSValue valueWithCMTime:midpoint];
[imageGenerator generateCGImagesAsynchronouslyForTimes:@[midTime] completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
if (result == AVAssetImageGeneratorSucceeded && image != NULL) {
UIImage *centerFrameImage = [[UIImage alloc] initWithCGImage:image];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(centerFrameImage);
}
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(nil);
}
});
}
}];
}
压缩视频是因为视频分辨率过高所生成的视频的大小太大了,对于移动设备来说,内存是不能太大的,如果不支持分片上传到服务器,或者不支持流上传、文件上传,而只能支持表单上传,那么必须要限制大小,压缩视频。
就像我们在使用某平台的视频的上传的时候,到现在还没有支持流上传,也不支持文件上传,只支持表单上传,导致视频大一点就会闪退。流上传是上传成功了,但是人家后台不识别,这一次让某平台坑坏了。直接用file上传,也传过去了,上传进度100%了,但是人家那边还是作为失败处理,无奈!
言归正传,压缩、导出视频,需要通过AVAssetExportSession来实现,我们需要指定一个preset,并判断是否支持这个preset,只有支持才能使用。
我们这里设置的preset为AVAssetExportPreset640x480,属于压缩得比较厉害的了,这需要根据服务器视频上传的支持程度而选择的。然后通过调用异步压缩并导出视频:
- (void)compressVideoWithVideoURL:(NSURL *)videoURL
savedName:(NSString *)savedName
completion:(void (^)(NSString *savedPath))completion {
// Accessing video by URL
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
// Find compatible presets by video asset.
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
// Begin to compress video
// Now we just compress to low resolution if it supports
// If you need to upload to the server, but server does't support to upload by streaming,
// You can compress the resolution to lower. Or you can support more higher resolution.
if ([presets containsObject:AVAssetExportPreset640x480]) {
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPreset640x480];
NSString *doc = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
NSString *folder = [doc stringByAppendingPathComponent:@"HYBVideos"];
BOOL isDir = NO;
BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:folder isDirectory:&isDir];
if (!isExist || (isExist && !isDir)) {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:folder
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error == nil) {
NSLog(@"目录创建成功");
} else {
NSLog(@"目录创建失败");
}
}
NSString *outPutPath = [folder stringByAppendingPathComponent:savedName];
session.outputURL = [NSURL fileURLWithPath:outPutPath];
// Optimize for network use.
session.shouldOptimizeForNetworkUse = true;
NSArray *supportedTypeArray = session.supportedFileTypes;
if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
session.outputFileType = AVFileTypeMPEG4;
} else if (supportedTypeArray.count == 0) {
NSLog(@"No supported file types");
return;
} else {
session.outputFileType = [supportedTypeArray objectAtIndex:0];
}
// Begin to export video to the output path asynchronously.
[session exportAsynchronouslyWithCompletionHandler:^{
if ([session status] == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion([session.outputURL path]);
}
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(nil);
}
});
}
}];
}
}
写入相册可以通过ALAssetsLibrary类来实现,它提供了写入相册的API,异步写入,完成是要回到主线程更新UI:
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 判断相册是否兼容视频,兼容才能保存到相册
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) {
[library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// 写入相册
if (error == nil) {
NSLog(@"写入相册成功");
} else {
NSLog(@"写入相册失败");
}
}
}];
}
});
- (UIViewController *)topViewController {
UIViewController *resultVC;
resultVC = [self _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (resultVC.presentedViewController) {
resultVC = [self _topViewController:resultVC.presentedViewController];
}
return resultVC;
}
- (UIViewController *)_topViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [self _topViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [self _topViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
return nil;
}
使用方法
UIViewController *topmostVC = [self topViewController];
/**
* 数组拆分
*
* @param array 数组
* @param subSize 大小
*
* @return 多个数组
*/
- (NSMutableArray *)splitArray: (NSArray *)array withSubSize : (int)subSize{
// 数组将被拆分成指定长度数组的个数
unsigned long count = array.count % subSize == 0 ? (array.count / subSize) : (array.count / subSize + 1);
// 用来保存指定长度数组的可变数组对象
NSMutableArray *arr = [[NSMutableArray alloc] init];
//利用总个数进行循环,将指定长度的元素加入数组
for (int i = 0; i < count; i ++) {
//数组下标
int index = i * subSize;
//保存拆分的固定长度的数组元素的可变数组
NSMutableArray *arr1 = [[NSMutableArray alloc] init];
//移除子数组的所有元素
[arr1 removeAllObjects];
int j = index;
//将数组下标乘以1、2、3,得到拆分时数组的最大下标值,但最大不能超过数组的总大小
while (j < subSize*(i + 1) && j < array.count) {
[arr1 addObject:[array objectAtIndexCheck:j]];
j += 1;
}
//将子数组添加到保存子数组的数组中
[arr addObject:[arr1 copy]];
}
return arr;
}
用法:UIImage *yourImage= [self imageWithImageSimple:image scaledToSize:CGSizeMake(210.0, 210.0)];
//压缩图片
- (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize
{
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);
// Tell the old image to draw in this newcontext, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
// Return the new image.
return newImage;
}
/*
* 判断这个Timer不为nil则停止并释放
* 如果不先停止可能会导致crash
*/
#define WVSAFA_DELETE_TIMER(timer) { \
if (timer != nil) { \
[timer invalidate]; \
[timer release]; \
timer = nil; \
} \
}
- (UIViewController *)viewController
{
UIViewController *viewController = nil;
UIResponder *next = self.nextResponder;
while (next)
{
if ([next isKindOfClass:[UIViewController class]])
{
viewController = (UIViewController *)next;
break;
}
next = next.nextResponder;
}
return viewController;
}
//方法一
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
//方法二
- (void)resetDefaults
{
NSUserDefaults * defs = [NSUserDefaults standardUserDefaults];
NSDictionary * dict = [defs dictionaryRepresentation];
for (id key in dict)
{
[defs removeObjectForKey:key];
}
[defs synchronize];
}
#pragma mark - 打印系统所有已注册的字体名称
void enumerateFonts()
{
for(NSString *familyName in [UIFont familyNames])
{
NSLog(@"%@",familyName);
NSArray *fontNames = [UIFont fontNamesForFamilyName:familyName];
for(NSString *fontName in fontNames)
{
NSLog(@"\t|- %@",fontName);
}
}
}
- (UIColor*) getPixelColorAtLocation:(CGPoint)point inImage:(UIImage *)image
{
UIColor* color = nil;
CGImageRef inImage = image.CGImage;
CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
if (cgctx == NULL) {
return nil; /* error */
}
size_t w = CGImageGetWidth(inImage);
size_t h = CGImageGetHeight(inImage);
CGRect rect = {0,0,w,}};
CGContextDrawImage(cgctx, rect, inImage);
unsigned char* data = CGBitmapContextGetData (cgctx);
if (data != NULL) {
int offset = 4*((w*round(point.y))+round(point.x));
int alpha = data[offset];
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];
color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:
(blue/255.0f) alpha:(alpha/255.0f)];
}
CGContextRelease(cgctx);
if (data) {
free(data);
}
return color;
}
第一种:
- (NSString *)reverseWordsInString:(NSString *)str
{
NSMutableString *newString = [[NSMutableString alloc] initWithCapacity:str.length];
for (NSInteger i = str.length - 1; i >= 0 ; i --)
{
unichar ch = [str characterAtIndex:i];
[newString appendFormat:@"%c", ch];
}
return newString;
}
//第二种:
- (NSString*)reverseWordsInString:(NSString*)str
{
NSMutableString *reverString = [NSMutableString stringWithCapacity:str.length];
[str enumerateSubstringsInRange:NSMakeRange(0, str.length) options:NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[reverString appendString:substring];
}];
return reverString;
}
默认情况下,当设备一段时间没有触控动作时,iOS会锁住屏幕。但有一些应用是不需要锁屏的,比如视频播放器。
[UIApplication sharedApplication].idleTimerDisabled = YES;
或
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
UIViewController *vc = [[UIViewController alloc] init];
UINavigationController *na = [[UINavigationController alloc] initWithRootViewController:vc];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
na.modalPresentationStyle = UIModalPresentationOverCurrentContext;
}
else
{
self.modalPresentationStyle=UIModalPresentationCurrentContext;
}
[self presentViewController:na animated:YES completion:nil];
editSCheme 里面有个选项叫叫做enable zoombie Objects 取消选中
//显示
defaults write com.apple.finder AppleShowAllFiles -bool true
killall Finder
//隐藏
defaults write com.apple.finder AppleShowAllFiles -bool false
killall Finder
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APPID"]];
+ (NSString *)transform:(NSString *)chinese
{
//将NSString装换成NSMutableString
NSMutableString *pinyin = [chinese mutableCopy];
//将汉字转换为拼音(带音标)
CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformMandarinLatin, NO);
NSLog(@"%@", pinyin);
//去掉拼音的音标
CFStringTransform((__bridge CFMutableStringRef)pinyin, NULL, kCFStringTransformStripCombiningMarks, NO);
NSLog(@"%@", pinyin);
//返回最近结果
return pinyin;
}
- (void)setStatusBarBackgroundColor:(UIColor *)color
{
UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
if ([statusBar respondsToSelector:@selector(setBackgroundColor:)])
{
statusBar.backgroundColor = color;
}
}
NSArray *viewcontrollers=self.navigationController.viewControllers;
if (viewcontrollers.count > 1)
{
if ([viewcontrollers objectAtIndex:viewcontrollers.count - 1] == self)
{
//push方式
[self.navigationController popViewControllerAnimated:YES];
}
}
else
{
//present方式
[self dismissViewControllerAnimated:YES completion:nil];
}
- (NSString *)getLaunchImageName
{
CGSize viewSize = self.window.bounds.size;
// 竖屏
NSString *viewOrientation = @"Portrait";
NSString *launchImageName = nil;
NSArray* imagesDict = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchImages"];
for (NSDictionary* dict in imagesDict)
{
CGSize imageSize = CGSizeFromString(dict[@"UILaunchImageSize"]);
if (CGSizeEqualToSize(imageSize, viewSize) && [viewOrientation isEqualToString:dict[@"UILaunchImageOrientation"]])
{
launchImageName = dict[@"UILaunchImageName"];
}
}
return launchImageName;
}
UIWindow * keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView * firstResponder = [keyWindow performSelector:@selector(firstResponder)];
if ([self.selectedController conformsToProtocol:@protocol(RefreshPtotocol)])
{
[self.selectedController performSelector:@selector(onTriggerRefresh)];
}
BOOL isView = [textView isDescendantOfView:self.view];
NSArray *array = [NSArray arrayWithObjects:@"2.0", @"2.3", @"3.0", @"4.0", @"10", nil];
CGFloat sum = [[array valueForKeyPath:@"@sum.floatValue"] floatValue];
CGFloat avg = [[array valueForKeyPath:@"@avg.floatValue"] floatValue];
CGFloat max =[[array valueForKeyPath:@"@max.floatValue"] floatValue];
CGFloat min =[[array valueForKeyPath:@"@min.floatValue"] floatValue];
NSLog(@"%f\n%f\n%f\n%f",sum,avg,max,min);
[textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
G: 公元时代,例如AD公元
yy: 年的后2位
yyyy: 完整年
MM: 月,显示为1-12
MMM: 月,显示为英文月份简写,如 Jan
MMMM: 月,显示为英文月份全称,如 Janualy
dd: 日,2位数表示,如02
d: 日,1-2位显示,如 2
EEE: 简写星期几,如Sun
EEEE: 全写星期几,如Sunday
aa: 上下午,AM/PM
H: 时,24小时制,0-23
K:时,12小时制,0-11
m: 分,1-2位
mm: 分,2位
s: 秒,1-2位
ss: 秒,2位
S: 毫秒
+ (NSArray *) allSubclasses
{
Class myClass = [self class];
NSMutableArray *mySubclasses = [NSMutableArray array];
unsigned int numOfClasses;
Class *classes = objc_copyClassList(&numOfClasses;);
for (unsigned int ci = 0; ci
}
NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
NSArray *proxies = (__bridge NSArray *)(CFNetworkCopyProxiesForURL((__bridge CFURLRef _Nonnull)([NSURL URLWithString:@"http://www.baidu.com"]), (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
NSLog(@"\n%@",proxies);
NSDictionary *settings = proxies[0];
NSLog(@"%@",[settings objectForKey:(NSString *)kCFProxyHostNameKey]);
NSLog(@"%@",[settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
NSLog(@"%@",[settings objectForKey:(NSString *)kCFProxyTypeKey]);
if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"])
{
NSLog(@"没代理");
}
else
{
NSLog(@"设置了代理");
}
+(NSString *)translation:(NSString *)arebic
{
NSString *str = arebic;
NSArray *arabic_numerals = @[@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"0"];
NSArray *chinese_numerals = @[@"一",@"二",@"三",@"四",@"五",@"六",@"七",@"八",@"九",@"零"];
NSArray *digits = @[@"个",@"十",@"百",@"千",@"万",@"十",@"百",@"千",@"亿",@"十",@"百",@"千",@"兆"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:chinese_numerals forKeys:arabic_numerals];
NSMutableArray *sums = [NSMutableArray array];
for (int i = 0; i
}
// Create NSData object
NSData *nsdata = [@"iOS Developer Tips encoded in Base64"
dataUsingEncoding:NSUTF8StringEncoding];
// Get NSString from NSData object in Base64
NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0];
// Print the Base64 encoded string
NSLog(@"Encoded: %@", base64Encoded);
// Let's go the other way...
// NSData from the Base64 encoded str
NSData *nsdataFromBase64String = [[NSData alloc]
initWithBase64EncodedString:base64Encoded options:0];
// Decoded NSString from the NSData
NSString *base64Decoded = [[NSString alloc]
initWithData:nsdataFromBase64String encoding:NSUTF8StringEncoding];
NSLog(@"Decoded: %@", base64Decoded);
UICollectionView在reloadItems的时候,默认会附加一个隐式的fade动画,有时候很讨厌,尤其是当你的cell是复合cell的情况下(比如cell使用到了UIStackView)。
//方法一
[UIView performWithoutAnimation:^{
[collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
}];
//方法二
[UIView animateWithDuration:0 animations:^{
[collectionView performBatchUpdates:^{
[collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
} completion:nil];
}];
//方法三
[UIView setAnimationsEnabled:NO];
[self.trackPanel performBatchUpdates:^{
[collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
} completion:^(BOOL finished) {
[UIView setAnimationsEnabled:YES];
}];
打开终端输入三条命令:
touch ~/.lldbinit
echo display @import UIKit >> ~/.lldbinit
echo target stop-hook add -o \"target stop-hook disable\" >> ~/.lldbinit
pod install --verbose --no-repo-update
pod update --verbose --no-repo-update
如果不加后面的参数,默认会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少
UIImage *image = [UIImage imageNamed:@"aa"];
NSUInteger size = CGImageGetHeight(image.CGImage) * CGImageGetBytesPerRow(image.CGImage);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(timer, ^{
//@"倒计时结束,关闭"
dispatch_source_cancel(timer);
dispatch_async(dispatch_get_main_queue(), ^{
});
});
dispatch_resume(timer);
- (UIImage *)imageWithTitle:(NSString *)title fontSize:(CGFloat)fontSize
{
//画布大小
CGSize size=CGSizeMake(self.size.width,self.size.height);
//创建一个基于位图的上下文
UIGraphicsBeginImageContextWithOptions(size,NO,0.0);//opaque:NO scale:0.0
[self drawAtPoint:CGPointMake(0.0,0.0)];
//文字居中显示在画布上
NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
paragraphStyle.alignment=NSTextAlignmentCenter;//文字居中
//计算文字所占的size,文字居中显示在画布上
CGSize sizeText=[title boundingRectWithSize:self.size options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}context:nil].size;
CGFloat width = self.size.width;
CGFloat height = self.size.height;
CGRect rect = CGRectMake((width-sizeText.width)/2, (height-sizeText.height)/2, sizeText.width, sizeText.height);
//绘制文字
[title drawInRect:rect withAttributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:fontSize],NSForegroundColorAttributeName:[ UIColor whiteColor],NSParagraphStyleAttributeName:paragraphStyle}];
//返回绘制的新图形
UIImage *newImage= UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (NSMutableArray *)allSubViewsForView:(UIView *)view
{
NSMutableArray *array = [NSMutableArray arrayWithCapacity:0];
for (UIView *subView in view.subviews)
{
[array addObject:subView];
if (subView.subviews.count > 0)
{
[array addObjectsFromArray:[self allSubViewsForView:subView]];
}
}
return array;
}
//文件大小
- (long long)fileSizeAtPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path])
{
long long size = [fileManager attributesOfItemAtPath:path error:nil].fileSize;
return size;
}
return 0;
}
//文件夹大小
- (long long)folderSizeAtPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
long long folderSize = 0;
if ([fileManager fileExistsAtPath:path])
{
NSArray *childerFiles = [fileManager subpathsAtPath:path];
for (NSString *fileName in childerFiles)
{
NSString *fileAbsolutePath = [path stringByAppendingPathComponent:fileName];
if ([fileManager fileExistsAtPath:fileAbsolutePath])
{
long long size = [fileManager attributesOfItemAtPath:fileAbsolutePath error:nil].fileSize;
folderSize += size;
}
}
}
return folderSize;
}
你是不是也遇到过这样的问题,一个button或者label,只要右边的两个角圆角,或者只要一个圆角。该怎么办呢。这就需要图层蒙版来帮助我们了
CGRect rect = view.bounds;
CGSize radio = CGSizeMake(30, 30);//圆角尺寸
UIRectCorner corner = UIRectCornerTopLeft|UIRectCornerTopRight;//这只圆角位置
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corner cornerRadii:radio];
CAShapeLayer *masklayer = [[CAShapeLayer alloc]init];//创建shapelayer
masklayer.frame = view.bounds;
masklayer.path = path.CGPath;//设置路径
view.layer.mask = masklayer;
floor(x),有时候也写做Floor(x),其功能是“下取整”,即取不大于x的最大整数 例如:
x=3.14,floor(x)=3
y=9.99999,floor(y)=9
与floor函数对应的是ceil函数,即上取整函数。
ceil函数的作用是求不小于给定实数的最小整数。
ceil(2)=ceil(1.2)=cei(1.5)=2.00
floor函数与ceil函数的返回值均为double型
//方法一:
- (int)convertToInt:(NSString*)strtemp
{
int strlength = 0;
char* p = (char*)[strtemp cStringUsingEncoding:NSUnicodeStringEncoding];
for (int i=0 ; i
}
UIImage *image = [UIImage imageNamed:@"image"];
self.MYView.layer.contents = (__bridge id _Nullable)(image.CGImage);
self.MYView.layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);
[scrollView.panGestureRecognizerrequireGestureRecognizerToFail:self.navigationController.interactivePopGestureRecognizer];
[[UIBarButtonItemappearance]setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60)forBarMetrics:UIBarMetricsDefault];
+ (BOOL)checkIsChinese:(NSString *)string
{
for (int i=0; i
}
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_enter(dispatchGroup);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"第一个请求完成");
dispatch_group_leave(dispatchGroup);
});
dispatch_group_enter(dispatchGroup);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"第二个请求完成");
dispatch_group_leave(dispatchGroup);
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"请求完成");
});
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// 四位加一个空格
if ([string isEqualToString:@""])
{
// 删除字符
if ((textField.text.length - 2) % 5 == 0)
{
textField.text = [textField.text substringToIndex:textField.text.length - 1];
}
return YES;
}
else
{
if (textField.text.length % 5 == 0)
{
textField.text = [NSString stringWithFormat:@"%@ ", textField.text];
}
}
return YES;
}
//获取私有属性 比如设置UIDatePicker的字体颜色
- (void)setTextColor
{
//获取所有的属性,去查看有没有对应的属性
unsigned int count = 0;
objc_property_t *propertys = class_copyPropertyList([UIDatePicker class], &count);
for(int i = 0;i
//获得成员变量 比如修改UIAlertAction的按钮字体颜色
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([UIAlertAction class], &count);
for(int i =0;i
Class c =NSClassFromString(@"LSApplicationWorkspace");
id s = [(id)c performSelector:NSSelectorFromString(@"defaultWorkspace")];
NSArray *array = [s performSelector:NSSelectorFromString(@"allInstalledApplications")];
for (id item in array)
{
NSLog(@"%@",[item performSelector:NSSelectorFromString(@"applicationIdentifier")]);
//NSLog(@"%@",[item performSelector:NSSelectorFromString(@"bundleIdentifier")]);
NSLog(@"%@",[item performSelector:NSSelectorFromString(@"bundleVersion")]);
NSLog(@"%@",[item performSelector:NSSelectorFromString(@"shortVersionString")]);
}
- (BOOL)isSameDateWithDate:(NSDate *)date
{
//日期间隔大于七天之间返回NO
if (fabs([self timeIntervalSinceDate:date]) >= 7 * 24 *3600)
{
return NO;
}
NSCalendar *calender = [NSCalendar currentCalendar];
calender.firstWeekday = 2;//设置每周第一天从周一开始
//计算两个日期分别为这年第几周
NSUInteger countSelf = [calender ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitYear forDate:self];
NSUInteger countDate = [calender ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitYear forDate:date];
//相等就在同一周,不相等就不在同一周
return countSelf == countDate;
}
//iOS8之后
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
//如果App没有添加权限,显示的是设定界面。如果App有添加权限(例如通知),显示的是App的设定界面。
//iOS8之前
//先添加一个url type如下图,在代码中调用如下代码,即可跳转到设置页面的对应项
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=WIFI"]];
About — prefs:root=General&path=About
Accessibility — prefs:root=General&path=ACCESSIBILITY
Airplane Mode On — prefs:root=AIRPLANE_MODE
Auto-Lock — prefs:root=General&path=AUTOLOCK
Brightness — prefs:root=Brightness
Bluetooth — prefs:root=General&path=Bluetooth
Date & Time — prefs:root=General&path=DATE_AND_TIME
FaceTime — prefs:root=FACETIME
General — prefs:root=General
Keyboard — prefs:root=General&path=Keyboard
iCloud — prefs:root=CASTLE
iCloud Storage & Backup — prefs:root=CASTLE&path=STORAGE_AND_BACKUP
International — prefs:root=General&path=INTERNATIONAL
Location Services — prefs:root=LOCATION_SERVICES
Music — prefs:root=MUSIC
Music Equalizer — prefs:root=MUSIC&path=EQ
Music Volume Limit — prefs:root=MUSIC&path=VolumeLimit
Network — prefs:root=General&path=Network
Nike + iPod — prefs:root=NIKE_PLUS_IPOD
Notes — prefs:root=NOTES
Notification — prefs:root=NOTIFICATI*****_ID
Phone — prefs:root=Phone
Photos — prefs:root=Photos
Profile — prefs:root=General&path=ManagedConfigurationList
Reset — prefs:root=General&path=Reset
Safari — prefs:root=Safari
Siri — prefs:root=General&path=Assistant
Sounds — prefs:root=Sounds
Software Update — prefs:root=General&path=SOFTWARE_UPDATE_LINK
Store — prefs:root=STORE
Twitter — prefs:root=TWITTER
Usage — prefs:root=General&path=USAGE
VPN — prefs:root=General&path=Network/VPN
Wallpaper — prefs:root=Wallpaper
Wi-Fi — prefs:root=WIFI
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endIgnoringInteractionEvents]
});
-(void)pauseLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
//通过NSNumberFormatter,同样可以设置NSNumber输出的格式。例如如下代码:
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
NSString *string = [formatter stringFromNumber:[NSNumber numberWithInt:123456789]];
NSLog(@"Formatted number string:%@",string);
//输出结果为:[1223:403] Formatted number string:123,456,789
//其中NSNumberFormatter类有个属性numberStyle,它是一个枚举型,设置不同的值可以输出不同的数字格式。该枚举包括:
typedef NS_ENUM(NSUInteger, NSNumberFormatterStyle) {
NSNumberFormatterNoStyle = kCFNumberFormatterNoStyle,
NSNumberFormatterDecimalStyle = kCFNumberFormatterDecimalStyle,
NSNumberFormatterCurrencyStyle = kCFNumberFormatterCurrencyStyle,
NSNumberFormatterPercentStyle = kCFNumberFormatterPercentStyle,
NSNumberFormatterScientificStyle = kCFNumberFormatterScientificStyle,
NSNumberFormatterSpellOutStyle = kCFNumberFormatterSpellOutStyle
};
//各个枚举对应输出数字格式的效果如下:其中第三项和最后一项的输出会根据系统设置的语言区域的不同而不同。
[1243:403] Formatted number string:123456789
[1243:403] Formatted number string:123,456,789
[1243:403] Formatted number string:¥123,456,789.00
[1243:403] Formatted number string:-539,222,988%
[1243:403] Formatted number string:1.23456789E8
[1243:403] Formatted number string:一亿二千三百四十五万六千七百八十九
在网页加载完成时,通过js获取图片和添加点击的识别方式
//UIWebView
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//这里是js,主要目的实现对url的获取
static NSString * const jsGetImages =
@"function getImages(){\
var objs = document.getElementsByTagName(\"img\");\
var imgScr = '';\
for(var i=0;i
}
//WKWebView
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
{
static NSString * const jsGetImages =
@"function getImages(){\
var objs = document.getElementsByTagName(\"img\");\
var imgScr = '';\
for(var i=0;i
CGFloat height = [[self.webView stringByEvaluatingJavaScriptFromString:@"document.body.offsetHeight"] floatValue];
//第一种方法
//导航栏纯透明
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
//去掉导航栏底部的黑线
self.navigationBar.shadowImage = [UIImage new];
//第二种方法
[[self.navigationBar subviews] objectAtIndex:0].alpha = 0;
[self.tabBar setBackgroundImage:[UIImage new]];
self.tabBar.shadowImage = [UIImage new];
//第一种
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offsetToShow = 200.0;//滑动多少就完全显示
CGFloat alpha = 1 - (offsetToShow - scrollView.contentOffset.y) / offsetToShow;
[[self.navigationController.navigationBar subviews] objectAtIndex:0].alpha = alpha;
}
//第二种
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat offsetToShow = 200.0;
CGFloat alpha = 1 - (offsetToShow - scrollView.contentOffset.y) / offsetToShow;
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
[self.navigationController.navigationBar setBackgroundImage:[self imageWithColor:[[UIColor orangeColor]colorWithAlphaComponent:alpha]] forBarMetrics:UIBarMetricsDefault];
}
//生成一张纯色的图片
- (UIImage *)imageWithColor:(UIColor *)color
{
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}
模拟器的位置:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs
文档安装位置:
/Applications/Xcode.app/Contents/Developer/Documentation/DocSets
插件保存路径:
~/Library/ApplicationSupport/Developer/Shared/Xcode/Plug-ins
自定义代码段的保存路径:
~/Library/Developer/Xcode/UserData/CodeSnippets/
如果找不到CodeSnippets文件夹,可以自己新建一个CodeSnippets文件夹。
描述文件路径
~/Library/MobileDevice/Provisioning Profiles
一般情况下,右边的item会和屏幕右侧保持一段距离: 下面是通过添加一个负值宽度的固定间距的item来解决,也可以改变宽度实现不同的间隔:
UIImage *img = [[UIImage imageNamed:@"icon_cog"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
//宽度为负数的固定间距的系统item
UIBarButtonItem *rightNegativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
[rightNegativeSpacer setWidth:-15];
UIBarButtonItem *rightBtnItem1 = [[UIBarButtonItem alloc]initWithImage:img style:UIBarButtonItemStylePlain target:self action:@selector(rightButtonItemClicked:)];
UIBarButtonItem *rightBtnItem2 = [[UIBarButtonItem alloc]initWithImage:img style:UIBarButtonItemStylePlain target:self action:@selector(rightButtonItemClicked:)];
self.navigationItem.rightBarButtonItems = @[rightNegativeSpacer,rightBtnItem1,rightBtnItem2];
NSString *string = @"http://abc.com?aaa=你好&bbb=tttee";
//编码 打印:http://abc.com?aaa=%E4%BD%A0%E5%A5%BD&bbb=tttee
string = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
//解码 打印:http://abc.com?aaa=你好&bbb=tttee
string = [string stringByRemovingPercentEncoding];
//设置
NSDictionary *dic = @{@"UserAgent":@"your UserAgent"};
[[NSUserDefaults standardUserDefaults] registerDefaults:dic];
//获取
NSString *agent = [self.WebView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfFileSystemForPath:NSHomeDirectory() error:nil];
NSLog(@"容量%.2fG",[attributes[NSFileSystemSize] doubleValue] / (powf(1024, 3)));
NSLog(@"可用%.2fG",[attributes[NSFileSystemFreeSize] doubleValue] / powf(1024, 3));
UIColor *color = [UIColor colorWithRed:0.2 green:0.3 blue:0.9 alpha:1.0];
const CGFloat *components = CGColorGetComponents(color.CGColor);
NSLog(@"Red: %.1f", components[0]);
NSLog(@"Green: %.1f", components[1]);
NSLog(@"Blue: %.1f", components[2]);
NSLog(@"Alpha: %.1f", components[3]);
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
[self.textField setValue:[UIFont boldSystemFontOfSize:16] forKeyPath:@"_placeholderLabel.font"];
AFJSONResponseSerializer *response = [AFJSONResponseSerializer serializer];
response.removesKeysWithNullValues = YES;
ceil()功 能:返回大于或者等于指定表达式的最小整数
floor()功 能:返回小于或者等于指定表达式的最大整数
UIWebView里面的图片自适应屏幕
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *js = @"function imgAutoFit() { \
var imgs = document.getElementsByTagName('img'); \
for (var i = 0; i < imgs.length; ++i) { \
var img = imgs[i]; \
img.style.maxWidth = %f; \
} \
}";
js = [NSString stringWithFormat:js, [UIScreen mainScreen].bounds.size.width - 20];
[webView stringByEvaluatingJavaScriptFromString:js];
[webView stringByEvaluatingJavaScriptFromString:@"imgAutoFit()"];
}
+ (NSDate *)dateFromISO8601StringDateFormatter:(NSString *)string locale:(NSLocale *)locale{
if (!string) {
return nil;
}
struct tm tm;
time_t t;
strptime([string cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%d %H:%M:%S", &tm);
tm.tm_isdst = -1;
t = mktime(&tm);
return [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]];
}
- (NSString *)ISO8601String:(NSDate*)date {
struct tm *timeinfo;
char buffer[80];
time_t rawtime = [date timeIntervalSince1970] - [[NSTimeZone localTimeZone] secondsFromGMT];
timeinfo = localtime(&rawtime);
strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
return [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
}
//创建
UIImageView *imageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
//图片
imageView.image = [UIImage imageNamed:@"1.jpeg"];
//背景颜色
imageView.backgroundColor = [UIColor yellowColor];
//设置图片内容模式
imageView.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:imageView];
//毛玻璃
UIToolbar *toolbar = [[UIToolbar alloc]initWithFrame:imageView.bounds];
//样式
toolbar.barStyle = UIBarStyleDefault;
//透明度
toolbar.alpha = 0.8f;
[imageView addSubview:toolbar];
关于TableView代理方法和其他一些数据与逻辑处理和平时一样,只是在下啦的时候啦到的数据,放到最前面,同事控制TableView的偏移。
self.oldSize = self.tableView.contentSize;
self.oldPoint = self.tableView.contentOffset;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// if (self.isfirst) {
for (int i = 0; i < 15; i++) {
[self.dataArray insertObject:[NSString stringWithFormat:@"新增加信息%d",i] atIndex:0];
}
// }
// self.isfirst = YES;
// 刷新表格
[self.tableView reloadData];
// (最好在刷新表格后调用)调用endRefreshing可以结束刷新状态
[self.tableView EndRefreshing];
CGSize newSize = self.tableView.contentSize;
CGPoint newPoint = CGPointMake(0, self.oldPoint.y+newSize.height - self.oldSize.height);
self.tableView.contentOffset = newPoint;
});
集成NSObject
-(NSMutableDictionary *)getKeychainQuery:(NSString *)service
{
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount,
(__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
nil];
}
+ (void)saveKeychainValue:(NSString *)sValue Key:(NSString *)sKey
{
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:sKey];
//Delete old item before add new item
SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:sValue] forKey:(__bridge_transfer id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
}
+ (NSString *)readKeychainValue:(NSString *)sKey
{
NSString *ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:sKey];
//Configure the search setting
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
[keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
<a href="http://www.jobbole.com/members/xyz937134366">@try</a> {
ret = (NSString *)[NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
} <a href="http://www.jobbole.com/members/wx895846013">@catch</a> (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", sKey, e);
} <a href="http://www.jobbole.com/members/finally">@finally</a> {
}
}
return ret;
}
+ (void)deleteKeychainValue:(NSString *)sKey {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:sKey];
SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
}
// ------------------------------------------------------------------
// --------------------- 以下是自定义图像处理部分 -----------------------
// ------------------------------------------------------------------
// 自定义裁剪算法
- (UIImage *)dealImage:(UIImage *)img cornerRadius:(CGFloat)c {
// 1.CGDataProviderRef 把 CGImage 转 二进制流
CGDataProviderRef provider = CGImageGetDataProvider(img.CGImage);
void *imgData = (void *)CFDataGetBytePtr(CGDataProviderCopyData(provider));
int width = img.size.width * img.scale;
int height = img.size.height * img.scale;
// 2.处理 imgData
// dealImage(imgData, width, height);
cornerImage(imgData, width, height, c);
// 3.CGDataProviderRef 把 二进制流 转 CGImage
CGDataProviderRef pv = CGDataProviderCreateWithData(NULL, imgData, width * height * 4, releaseData);
CGImageRef content = CGImageCreate(width , height, 8, 32, 4 * width, CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, pv, NULL, true, kCGRenderingIntentDefault);
UIImage *result = [UIImage imageWithCGImage:content];
CGDataProviderRelease(pv); // 释放空间
CGImageRelease(content);
return result;
}
void releaseData(void *info, const void *data, size_t size) {
free((void *)data);
}
// 在 img 上处理图片, 测试用
void dealImage(UInt32 *img, int w, int h) {
int num = w * h;
UInt32 *cur = img;
for (int i=0; i<num; i++, cur++) {
UInt8 *p = (UInt8 *)cur;
// RGBA 排列
// f(x) = 255 - g(x) 求负片
p[0] = 255 - p[0];
p[1] = 255 - p[1];
p[2] = 255 - p[2];
p[3] = 255;
}
}
// 裁剪圆角
void cornerImage(UInt32 *const img, int w, int h, CGFloat cornerRadius) {
CGFloat c = cornerRadius;
CGFloat min = w > h ? h : w;
if (c < 0) { c = 0; }
if (c > min * 0.5) { c = min * 0.5; }
// 左上 y:[0, c), x:[x, c-y)
for (int y=0; y<c; y++) {
for (int x=0; x<c-y; x++) {
UInt32 *p = img + y * w + x; // p 32位指针,RGBA排列,各8位
if (isCircle(c, c, c, x, y) == false) {
*p = 0;
}
}
}
// 右上 y:[0, c), x:[w-c+y, w)
int tmp = w-c;
for (int y=0; y<c; y++) {
for (int x=tmp+y; x<w; x++) {
UInt32 *p = img + y * w + x;
if (isCircle(w-c, c, c, x, y) == false) {
*p = 0;
}
}
}
// 左下 y:[h-c, h), x:[0, y-h+c)
tmp = h-c;
for (int y=h-c; y<h; y++) {
for (int x=0; x<y-tmp; x++) {
UInt32 *p = img + y * w + x;
if (isCircle(c, h-c, c, x, y) == false) {
*p = 0;
}
}
}
// 右下 y~[h-c, h), x~[w-c+h-y, w)
tmp = w-c+h;
for (int y=h-c; y<h; y++) {
for (int x=tmp-y; x<w; x++) {
UInt32 *p = img + y * w + x;
if (isCircle(w-c, h-c, c, x, y) == false) {
*p = 0;
}
}
}
}
// 判断点 (px, py) 在不在圆心 (cx, cy) 半径 r 的圆内
static inline bool isCircle(float cx, float cy, float r, float px, float py) {
if ((px-cx) * (px-cx) + (py-cy) * (py-cy) > r * r) {
return false;
}
return true;
}
// 其他图像效果可以自己写函数,然后在 dealImage: 中调用 otherImage 即可
void otherImage(UInt32 *const img, int w, int h) {
// 自定义处理
}
//隐藏阴影线
[[UITabBar appearance] setShadowImage:[UIImage new]];
- (void)setupTabBarBackgroundImage {
UIImage *image = [UIImage imageNamed:@"tab_bg"];
CGFloat top = 40; // 顶端盖高度
CGFloat bottom = 40 ; // 底端盖高度
CGFloat left = 100; // 左端盖宽度
CGFloat right = 100; // 右端盖宽度
UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
// 指定为拉伸模式,伸缩后重新赋值
UIImage *TabBgImage = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
self.tabBar.backgroundImage = TabBgImage;
[[UITabBar appearance] setShadowImage:[UIImage new]];
[[UITabBar appearance] setBackgroundImage:[[UIImage alloc]init]];
}
//自定义TabBar高度
- (void)viewWillLayoutSubviews {
CGRect tabFrame = self.tabBar.frame;
tabFrame.size.height = 60;
tabFrame.origin.y = self.view.frame.size.height - 60;
self.tabBar.frame = tabFrame;
}
- (void)viewWillAppear:(BOOL)animated{
// Called when the view is about to made visible. Default does nothing
[super viewWillAppear:animated];
//去除导航栏下方的横线
[navigationBar setBackgroundImage:[UIImage imageWithColor:[self colorFromHexRGB:@"33cccc"]]
forBarPosition:UIBarPositionAny
barMetrics:UIBarMetricsDefault];
[navigationBar setShadowImage:[UIImage new]];
}
这是唯一一个隐藏这条线的官方用法,但是有一个缺陷-删除了translucency(半透明)
1)声明UIImageView变量,存储底部横线
@interface MyViewController {
UIImageView *navBarHairlineImageView;
}
2)在viewDidLoad中加入:
navBarHairlineImageView = [self findHairlineImageViewUnder:self.navigationController.navigationBar];
3)实现找出底部横线的函数
- (UIImageView *)findHairlineImageViewUnder:(UIView *)view {
if ([view isKindOfClass:UIImageView.class] && view.bounds.size.height <= 1.0) {
return (UIImageView *)view;
}
for (UIView *subview in view.subviews) {
UIImageView *imageView = [self findHairlineImageViewUnder:subview];
if (imageView) {
return imageView;
}
}
return nil;
}
4)最后在viewWillAppear,viewWillDisappear中处理
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
navBarHairlineImageView.hidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
navBarHairlineImageView.hidden = NO;
}
###两个范围的富文本
NSString *times = [NSString stringWithFormat:@"哇塞!本次视频聊天%@", [info objectStringForKey:@"times"]];
NSString *type = [NSString stringWithFormat:@"%@", [info objectStringForKey:@"type"]];
NSString *counts = nil;
if ([type isEqualToString:@"1"]) {
counts = [NSString stringWithFormat:@"消耗%@能量", [info objectStringForKey:@"counts"]];
} else {
counts = [NSString stringWithFormat:@"赚了%@积分", [info objectStringForKey:@"counts"]];
}
NSString *formatString = [NSString stringWithFormat:@"%@,%@", times, counts];
NSMutableAttributedString *AttributedStr = [[NSMutableAttributedString alloc]initWithString:formatString];
NSRange range = [formatString rangeOfString:@","];
[AttributedStr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"#fb455a"] range:NSMakeRange(9, range.location - 9)];
[AttributedStr addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithHexString:@"#fb455a"] range:NSMakeRange(range.location + 3, formatString.length - range.location - 5)];
// 在 viewDidLoad 中创建
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:nil message:AttributedStr.string preferredStyle:UIAlertControllerStyleAlert];
// 用 KVC 修改其 没有暴露出来的
// [alertVC setValue:AttributedTit forKey:@“attributedTitle”]; [alertVC setValue:AttributedStr forKey:@“attributedMessage”];
//修改按钮的颜色,同上可以使用同样的方法修改内容,样式
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];
[defaultAction setValue:[UIColor blackColor] forKey:@"_titleTextColor"];
[alertVC addAction:defaultAction];
[self presentViewController:alertVC animated:YES completion:nil];
上面使用了一种个人比较喜欢的方法,
总体来说,第二种办法还是很好地,建议大家使用第二种办法。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
我们知道当给一个对象发送一个方法的时候, 如果当前类和父类都没实现该方法的时候就会走转发流程
动态方法解析 -> 快速消息转发 -> 标准消息转发
先看一下SEL的概念,Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。
SEL也是@selector的类型,用来表示OC运行时的方法的名字。来看一下OC中的定义
本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。这个查找过程我们将在下面说明。
我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector。
实际上是一个函数指针,指向方法实现的首地址,定义如下:
使用当前CPU架构实现的标准的C调用约定
第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
第二个参数是方法选择器(selector),
第三个参数开始是方法的实际参数列表。
通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些,当然必须说明的是,这种方式只适用于极特殊的优化场景,如效率敏感的场景下大量循环的调用某方法。
直接上定义:
Method = SEL + IMP + method_types,相当于在SEL和IMP之间建立了一个映射
相关方法:
// 给 cls 添加一个新方法
BOOL class_addMethod (
Class cls,
SEL name,
IMP imp,
const charchar *types
);
// 替换 cls 里的一个方法的实现
IMP class_replaceMethod (
Class cls,
SEL name,
IMP imp,
const charchar *types
);
// 返回 cls 的指定方法
Method class_getInstanceMethod (
Class cls,
SEL name
);
// 设置一个方法的实现
IMP method_setImplementation (
Method m,
IMP imp
);
// 返回 cls 里的 name 方法的实现
IMP class_getMethodImplementation (
Class cls,
SEL name
);
objc_msgSend(receiver, selector, arg1,arg2,…)
具体的过程如下:
先找到selector 对应的方法实现(IMP),因为同一个方法可能在不同的类中有不同的实现,所以需要receiver的类来找到确切的IMP
IMP class_getMethodImplementation(Class class, SEL selector)
如同其文档所说:
The function pointer returned may be a function internal to the runtime instead of an actual method implementation. For example, if instances of the class do not respond to the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
具体来说,当找不到IMP的时候,方法返回一个 _objc_msgForward 对象,用来标记需要转入消息转发流程,我们现在用的AOP框架也是利用了这个机制来人为的制造找不到IMP的假象来触发消息转发的流程
如果实在对_objc_msgFroward的内部实现感兴趣,只能看看源码了,只不过都是汇编实现的....感兴趣的同学可以想想为什么是用汇编来实现
这里有个源码的镜像https://github.com/opensource-apple ,如果翻墙费劲的话
根据查找结果
找到了IMP,调用找到的IMP,传入参数
没找到IMP,转入消息转发流程
将IMP的返回值作为自己的返回值
补充说明一下IMP的查找过程,消息传递的关键在于objc_class结构体中的以下几个东西:
Class *isa
Class *super_class
objc_method_list **methodLists
objc_cache *cache
当消息发送给一个对象时,objc_msgSend通过对象的isa获取到类的结构体,然后在cache和methodLists中查找,如果没找到就找其父类,以此类推知道找到NSObject类,如果还没找到,就走消息转发流程。
从上文中我们看到当obj无法查找到 IMP时,会返回一个特定的IMP _objc_msgForward , 然后会进入消息转发流程,具体流程如下:
resolveInstanceMethod:解析实例方法
resolveClassMethod:解析类方法
通过class_addMethod的方式将缺少的selector动态创建出来,前提是有提前实现好的IMP(method_types一致)
这种方案更多的是位@dynamic属性准备的
如果上一步没有处理,runtime会调用以下方法
-(id)forwardingTargetForSelector:(SEL)aSelector
如果该方法返回非nil的对象,则使用该对象作为新的消息接收者,不能返回self,会出现无限循环
如果不知道该返回什么,应该使用[super forwardingTargetForSelector:aSelector]
这种方法属于单纯的转发,无法对消息的参数和返回值进行处理
- (void)forwardInvocation:(NSInvocation *)anInvocation
对象需要创建一个NSInvocation对象,把消息调用的全部细节封装进去,包括selector, target, arguments 等参数,还能够对返回结果进行处理 为了使用完整转发,需要重写以下方法
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,如果2中return nil,执行methodSignatureForSelector:
因为消息转发机制为了创建NSInvocation需要使用这个方法吗获取信息,重写它为了提供合适的方法签名
到了有意思的戏肉部分,打算用流程图的方式解析一下核心的两个流程:拦截器(intercepter)注册流程和拦截器(intercepter)执行流程。
说明:(图中m:代表Method,ClassA是AOP的目标类,X是AOP的目标方法,AOPAspect是AOP处理类-单例)
1. 将原始的X的IMP拿出来,以特定的命名规则动态加入AOPAspect
2. 将X的IMP替换为_objc_msgForward,用这种比较tricky的方式来触发消息转发流程
3. 将ClassA中原有的forwardingTargetForSelector:的IMP以特定的命名规则存入AOPAspect
4. 将ClassA的forwardingTargetForSelector:的IMP用AOPApect中的baseClassForwardingTargetForSelector替换,其中的具体逻辑见下面的代码
后边的就是将拦截器的信息和block存入到AOPAspect中,细节就不讲了,有兴趣的同学可以到github上看看原始版
说明:(图中m:代表Method,ClassA是AOP的目标类,X是AOP的目标方法,AOPAspect是AOP处理类-单例,IMP是方法对应的实现)
开始调用,objc_msgSend开始查找SEL为X的IMP,查到结果为_objc_msgForward,触发ClassA的转发流程
1. ClassA中转发流程调用forwardingTargetForSelector:,实际会调用替换上去的baseClassForwardingTargetForSelector:的IMP,这个IMP正常情况下会返回AOPAspect的单例作为target(代码见上文图)
2. 接下来开始在AOPAspect的单例中执行转发流程,经过一系列的3.1-3.5的跳转查找,最终会触发转发流程的forwardingInvocation方法
3. 在forwardingInvocation中触发一系列的interceptors的执行(包括原始的X的IMP),代码见下图
4. 后边的interceptor的执行细节也略过了,有兴趣的同学可以到github上看看原始版
这里举个例子,我们有个方法sumA:andB:, 用来返回ab之和的一个字串,我们在这个方法前和方法后都增加个一段代码
在运行方法前我们把参数改成2和3, 当然这里是演示用,实际用的时候别改参数,不然其他同事真的要骂人了
在运行方法后我们输出传入的参数和返回值
在CODE上查看代码片派生到我的代码片
- (void)clickTestAop:(id)sender
{
AopTestM *test = [[AopTestM alloc] init];
NSLog(@"run1");
[test sumA:1 andB:2];
NSString *before = [XYAOP interceptClass:[AopTestM class] beforeExecutingSelector:@selector(sumA:andB:) usingBlock:^(NSInvocation *invocation) {
int a = 3;
int b = 4;
[invocation setArgument:&a atIndex:2];
[invocation setArgument:&b atIndex:3];
NSLog(@"berore fun. a = %d, b = %d", a , b);
}];
NSString *after = [XYAOP interceptClass:[AopTestM class] afterExecutingSelector:@selector(sumA:andB:) usingBlock:^(NSInvocation *invocation) {
int a;
int b;
NSString *str;
[invocation getArgument:&a atIndex:2];
[invocation getArgument:&b atIndex:3];
[invocation getReturnValue:&str];
NSLog(@"after fun. a = %d, b = %d, sum = %@", a , b, str);
}];
NSLog(@"run2");
[test sumA:1 andB:2];
[XYAOP removeInterceptorWithIdentifier:before];
[XYAOP removeInterceptorWithIdentifier:after];
NSLog(@"run3");
[test sumA:1 andB:2];
}
- (NSString *)sumA:(int)a andB:(int)b
{
int value = a + b;
NSString *str = [NSString stringWithFormat:@"fun running. sum : %d", value];
NSLog(@"%@", str);
return str;
}
我们执行这段代码的时候,大伙猜猜结果是啥.结果如下
2014-10-28 22:52:47.215 JoinShow[3751:79389] run1
2014-10-28 22:52:52.744 JoinShow[3751:79389] fun running. sum : 3
2014-10-28 22:52:52.745 JoinShow[3751:79389] run2
2014-10-28 22:52:52.745 JoinShow[3751:79389] berore fun. a = 3, b = 4
2014-10-28 22:52:52.745 JoinShow[3751:79389] fun running. sum : 7
2014-10-28 22:52:52.745 JoinShow[3751:79389] after fun. a = 3, b = 4, sum = fun running. sum : 7
2014-10-28 22:52:52.746 JoinShow[3751:79389] run3
2014-10-28 22:52:52.746 JoinShow[3751:79389] fun running. sum : 3
一个简洁高效的用于使iOS支持AOP面向切面编程的库.它可以帮助你在不改变一个类或类实例的代码的前提下,有效更改类的行为.比iOS传统的 AOP方法,更加简单高效.支持在方法执行的前/后或替代原方法执行.曾经是 PSPDFKit 的一部分,PSPDFKit,在Dropbox和Evernote中都有应用,现在单独单独开源出来给大家使用.
最新实例:点击下载
注: AOP是一种完全不同于OOP的设计模式.更多信息,可以参考这里: AOP 百度百科
CocoaPods 安装
pod "Aspects"
手动安装
把文件 Aspects.h/m 拖到工程中即可.
Aspects 用于支持AOP(面向切面编程)模式,用于部分解决OOP(面向对象)模式无法解决的特定问题.具体指的是那些在多个方法有交叉,无法或很难被有效归类的操作,比如:
不论何时用户通过客户端获取服务器端数据,权限检查总是必须的.
不论何时用户和市场交互,总应该更具用户的操作提供相应地购买参考或相关商品.
所有需要日志记录的操作.
Aspects 给 NSObject 扩展了下面的方法:
/// 为一个指定的类的某个方法执行前/替换/后,添加一段代码块.对这个类的所有对象都会起作用.
///
/// @param block 方法被添加钩子时,Aspectes会拷贝方法的签名信息.
/// 第一个参数将会是 `id<AspectInfo>`,余下的参数是此被调用的方法的参数.
/// 这些参数是可选的,并将被用于传递给block代码块对应位置的参数.
/// 你甚至使用一个没有任何参数或只有一个`id<AspectInfo>`参数的block代码块.
///
/// @注意 不支持给静态方法添加钩子.
/// @return 返回一个唯一值,用于取消此钩子.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
/// 为一个指定的对象的某个方法执行前/替换/后,添加一段代码块.只作用于当前对象.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;
/// 撤销一个Aspect 钩子.
/// @return YES 撤销成功, 否则返回 NO.
id<AspectToken> aspect = ...;
[aspect remove];
所有的调用,都会是线程安全的.Aspects 使用了Objective-C 的消息转发机会,会有一定的性能消耗.所有对于过于频繁的调用,不建议使用 Aspects.Aspects更适用于视图/控制器相关的等每秒调用不超过1000次的代码.
可以在调试应用时,使用Aspects动态添加日志记录功能.
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"控制器 %@ 将要显示: %tu", aspectInfo.instance, animated);
} error:NULL];
使用它,分析功能的设置会很简单:
https://github.com/orta/ARAnalytics
你可以在你的测试用例中用它来检查某个方法是否被真正调用(当涉及到继承或类目扩展时,很容易发生某个父类/子类方法未按预期调用的情况):
- (void)testExample {
TestClass *testClass = [TestClass new];
TestClass *testClass2 = [TestClass new];
__block BOOL testCallCalled = NO;
[testClass aspect_hookSelector:@selector(testCall) withOptions:AspectPositionAfter usingBlock:^{
testCallCalled = YES;
} error:NULL];
[testClass2 testCallAndExecuteBlock:^{
[testClass testCall];
} error:NULL];
XCTAssertTrue(testCallCalled, @"调用testCallAndExecuteBlock 必须调用 testCall");
}
它对调试应用真的会提供很大的作用.这里我想要知道究竟何时轻击手势的状态发生变化(如果是某个你自定义的手势的子类,你可以重写setState:方法来达到类似的效果;但这里的真正目的是,捕捉所有的各类控件的轻击手势,以准确分析原因):
[_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
NSLog(@"%@: %@", aspectInfo.instance, aspectInfo.arguments);
} error:NULL];
下面是一个你监测一个模态显示的控制器何时消失的示例.通常,你也可以写一个子类,来实现相似的效果,但使用 Aspects 可以有效减小你的代码量:
@implementation UIViewController (DismissActionHook)
// Will add a dismiss action once the controller gets dismissed.
- (void)pspdf_addWillDismissAction:(void (^)(void))action {
PSPDFAssert(action != NULL);
[self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
if ([aspectInfo.instance isBeingDismissed]) {
action();
}
} error:NULL];
}
@end
Aspectes 会自动标记自己,所有很容易在调用栈中查看某个方法是否已经调用:
在返回值不为void的方法上使用 Aspects
你可以使用 NSInvocation 对象类自定义返回值:
[PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> info, NSSet *touches, UIEvent *event) {
// 调用方法原来的实现.
BOOL processTouches;
NSInvocation *invocation = info.originalInvocation;
[invocation invoke];
[invocation getReturnValue:&processTouches];
if (processTouches) {
processTouches = pspdf_stylusShouldProcessTouches(touches, event);
[invocation setReturnValue:&processTouches];
}
} error:NULL];
当应用于某个类时(使用类方法添加钩子),不能同时hook父类和子类的同一个方法;否则会引起循环调用问题.但是,当应用于某个类的示例时(使用实例方法添加钩子),不受此限制. 使用KVO时,最好在 aspect_hookSelector: 调用之后添加观察者;否则可能会引起崩溃.
最后:如果你对ios开发中的响应式编程,链式编程,函数式编程也有研究或者比较感兴趣,可以私聊我,或者一起交流学习!
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
在Xcode启动的时候,Xcode将会寻找位于~/Library/Application Support/Developer/Shared/Xcode/Plug-ins文件夹中的后缀名为.xcplugin的bundle作为插件进行加载(运行其中的可执行文件)。Xcode5 Plugins 开发简介 写个自己的Xcode4插件
Xcode 4 插件制作入门 - Xcode 4 插件制作入门:Xcode所使用的所有库都包含在Xcode.app/Contents/的Frameworks,SharedFrameworks和OtherFrameworks三个文件夹下。其中和Xcode关系最为直接以及最为重要的是Frameworks中的IDEKit和IDEFoundation,以及SharedFrameworks中的DVTKit和DVTFoundation四个。
RTImageAssets - 一个 Xcode 插件,用来生成 @3x 的图片资源对应的 @2x 和 @1x 版本。Asset Catalog Creator 功能强大,能自动生成全部尺寸:包括App Icons、Image Sets、Launch Screens Generator。
VVDocumenter-Xcode - 一个Xcode插件,build后,随手打开一个你之前的项目,然后在任意一个方法上面连按三下"/“键盘,就ok了。
Reveal-Plugin-for-XCode - 一个Reveal插件,可以使工程不作任何修改的情况下使用Reveal,该插件已在Alcatraz上架。Reveal:分析iOS UI的利器 。
java2Objective-c - Google公司出得java转Obje-C转换工具,转换逻辑,不转换UI。
RegX - 专治代码强迫症的 Xcode 插件,使用 Swift 和 Objective-C 编写。其用竖向对齐特定源代码的元素,使得代码更易读和易理解。说明 ; 菜单:xcode——》Edit-》Regx 。
KSImageNamed - 自动完成,特别是如果你正在写Objective-C,如果Xcode能自动完成文件名难道不会很伟大吗?比如图像文件的名称。
FuzzyAutocomplete - Xcode的实现自动完成还不完美,此插件能给出你所期望或想要的建议,设置:xcode-》Editor-》FuzzyAutocomplete-》plugin settings。
GitDiff - Xcode的代码编辑器的一个微妙的补强,加上了足够的可见信息以了解上次git提交以来发生了什么变化,设置:xcode-》Edit-》GitDiff。
XToDo - 这个插件不仅凸显TODO,FIXME,???,以及!!!注释,也在便利列表呈现他们。 菜单:xcode-》view-》snippets; 调出列表显示: xcode-》view-》ToDo List : ctrl + T 。
Backlight - 突出显示当前正在编辑的行。菜单:xcode-》view-》Backlight 。
CocoaPods - 该CocoaPods的插件增加了一个CocoaPods菜单到Xcode的产品菜单。如果你不喜欢命令行,那么你一定会喜欢这个插件。 用CocoaPods做iOS程序的依赖管理 。
Peckham - 添加import语句比较麻烦,此插件 按Command-Control-P,给出的选项列表中选择要的头文件。先要安装Alcatraz ,在终端输入: curl -fsSL https://raw.github.com/supermarin/Alcatraz/master/Scripts/install.sh | sh ; 重启xcode-》window-》Package Manager:搜索 Peckham 安装,打开Peckham.xcodeproj,编译 Peckham target,重启Xcode 。
Auto-Importer - Auto-Importer是一个自动导入类对应的头文件的Xcode插件。
KSHObjcUML -KSHObjcUML 是一个 Objective-C 类引用关系图的 Xcode 插件。
ColorSense-for-Xcode - 颜色插件,安装之后,就不用根据RGB选择颜色,直接从取色板中取颜色,会自动补齐RGB代码。。
10款提高iOS开发效率的XCode插件 - 10款提高iOS开发效率的XCode插件:1. XcodeColors;5. ACCodeSnippetRepository;10. Dash for Xcode。
ZLGotoSandboxPlugin - 支持Xcode快捷键了跳转当前应用沙盒了!快捷键是 Shift+Common+w。
XcodeSwiftSnippets - XcodeSwiftSnippets, 提供了很多可在 Xcode 上使用的 Swift 代码片段, 通过自动补全的方式极大的提高了开发效率, 另外还有 Objective-C 版的。
CoPilot - 通过此插件, Xcode 可以协同编程了(采用 WebSocket 通讯)。如此强大的“黑工具”,不爱它能行吗。
第三方接口 - 基本所有第三方接口都在这,再也不用那么麻烦去找了。
提高iOS开发效率的方法和工具 - 提高iOS开发效率的方法和工具。
open-source-ios-apps - iOS App集合,分:swift与Objective-C–国外人整理。
适合iOS开发者的15大网站推荐 - 适合 iOS 开发者的 15 大网站推荐 — 英文网站。
Objective-C GitHub 排名前 100 项目简介 - 主要对当前 GitHub 排名前 100 的项目做一个简单的简介, 方便初学者快速了解到当前 Objective-C 在 GitHub 的情况。
Github-iOS备忘 -整理了比较常用的iOS第三方组件,以及github上的统计。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
其中有两个比较麻烦的
1:关于导航栏的适配
2:关于tabBar的适配
下面一个个整理了一下!
去除方法:选择Xcode ->Product ->Scheme -> Edit Scheme 或者按command + shift + < 快捷键,
在弹出的窗口中Environment Variables 下添加 0S_ACTIVITY_MODE=disable
注:真机调试不输出NSlog了,所以我真机调试的时候,把此处对号去除,就好了
#ifdef DEBUG
#define iCocosLog(format, ...) printf("\n[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define iCocosLog(format, ...)
#endif
我勾选了Automatically manage signing(需要在Xcode的偏好设置中,添加苹果账号),并且选择配置了Team,就好了。
注:或者另外一种方式 点击打开链接
解决办法:项目中访问了隐私数据,需要在info.plist中添加这些权限:
相机权限
<key>NSCameraUsageDescription</key>
<string>cameraDesciption</string>
相册权限
<key>NSPhotoLibraryUsageDescription</key>
<string>photoLibraryDesciption</string>
注: 在CODE上查看代码片派生到我的代码片
<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<!-- 位置 -->
<key>NSLocationUsageDescription</key>
<string>App需要您的同意,才能访问位置</string>
<!-- 在使用期间访问位置 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意,才能在使用期间访问位置</string>
<!-- 始终访问位置 -->
<key>NSLocationAlwaysUsageDescription</key>
<string>App需要您的同意,才能始终访问位置</string>
<!-- 日历 -->
<key>NSCalendarsUsageDescription</key>
<string>App需要您的同意,才能访问日历</string>
<!-- 提醒事项 -->
<key>NSRemindersUsageDescription</key>
<string>App需要您的同意,才能访问提醒事项</string>
<!-- 运动与健身 -->
<key>NSMotionUsageDescription</key> <string>App需要您的同意,才能访问运动与健身</string>
<!-- 健康更新 -->
<key>NSHealthUpdateUsageDescription</key>
<string>App需要您的同意,才能访问健康更新 </string>
<!-- 健康分享 -->
<key>NSHealthShareUsageDescription</key>
<string>App需要您的同意,才能访问健康分享</string>
<!-- 蓝牙 -->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>App需要您的同意,才能访问蓝牙</string>
<!-- 媒体资料库 -->
<key>NSAppleMusicUsageDescription</key>
<string>App需要您的同意,才能访问媒体资料库</string>
如果没有用,需配置一下
注意,添加的时候,末尾不要有空格,值得说明必须要要写不写也会崩溃
我们需要打开info.plist文件添加相应权限的说明,否则程序在iOS10上会出现崩溃。
经有的朋友提醒,发现程序内原来2个字的宽度是24,现在2个字需要27的宽度来显示了。。我只能试着一个个智能逐一排查!
在CODE上查看代码片派生到我的代码片
- (void)awakeFromNib {
// Initialization code
}
需要添加: 在CODE上查看代码片派生到我的代码片
[super awakeFromNib];
在iOS 10以前,我们要想使用应用程序去打开一个网页或者进行跳转,直接使用[[UIApplication sharedApplication] openURL 方法就可以了,但是在iOS 10 已经被废弃了,因为使用这种方式,处理的结果我们不能拦截到也不能获取到,对于开发是非常不利的,在iOS 10全新的退出了
[[UIApplication sharedApplication] openURL:nil options:nil completionHandler:nil];
有一个成功的回调block 可以进行监视。
注:仍然可以用,只不过会出现警告
现在改用: 在CODE上查看代码片派生到我的代码片
#define LIOS10_OR_LATER ([[[UIDevice currentDevice]systemVersion]compare:@"10.0" options:NSNumericSearch] !=NSOrderedAscending)
解决方法:
打开终端,命令运行: sudo /usr/libexec/xpccachectl
然后必须重启电脑后生效
Xcode8已经不能再使用第三方插件了,但是Xcode8已经完善了一部分第三方插件才能实现的功能(抹杀了第三方插件作者,掠夺别人的劳动成果),比如语法提示、代码注释。
Xcode8代码注释快捷键为 Command + Option + / 。
因为使用了"UINavigationBar+Awesome.h"这个框架,所以,最后找来找去,找到了这个框架的底层,修改代码发现既然可以。
if (!self.overlay) {
[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds) + 20)];
self.overlay.userInteractionEnabled = NO;
self.overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth; // Should not set `UIViewAutoresizingFlexibleHeight`
[[self.subviews firstObject] insertSubview:self.overlay atIndex:0];
}
self.overlay.backgroundColor = backgroundColor;
解决问题 找到原因了,修改代码就比较容易了,你可以在添加视图时,将bgView指定到UIVisualEffectView,将新的视图添加到UIVisualEffectView上:
for (UIView * v in subs)
{
NSString * classname = NSStringFromClass([v class]);
if ([classname isEqualToString:@"_UINavigationBarBackground"] || [classname isEqualToString:@"UINavigationBarBackground"])
{
bgview=v;
break;
} else if ([classname isEqualToString:@"_UIBarBackground"]) {
//适配iOS10导航
for (UIView *vi in v.subviews) {
NSString *viName = NSStringFromClass([vi class]);
if ([viName isEqualToString:@"UIVisualEffectView"]) {
bgview = vi;
break;
}
}
}
}
也可以还添加到_UIBarBackground上,但是找到UIVisualEffectView,将其隐藏掉:
if ([classname isEqualToString:@"_UINavigationBarBackground"] || [classname isEqualToString:@"UINavigationBarBackground"])
{
bgview=v;
break;
} else if ([classname isEqualToString:@"_UIBarBackground"]) {
bgview = v;
for (UIView *vi in v.subviews) {
// 适配iOS10
NSString *viName = NSStringFromClass([vi class]);
if ([viName isEqualToString:@"UIVisualEffectView"]) {
vi.hidden = YES;
break;
}
}
}
控制器报如下错误:
This version does not support documents saved in the Xcode 8 format. Open this document with Xcode 8.0 or later.
右键SB,选择Open As -> Source Code,并删除下面代码即可:
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
如下图的部分,不要忘记打开。所有的推送平台,不管是极光还是什么的,要想收到推送,这个是必须打开的哟✌️
之后就应该可以收到推送了。另外,极光推送也推出新版本了,大家也可以更新下。
PS.苹果这次对推送做了很大的变化,希望大家多查阅查阅,处理推送的代理方法也变化了。
// 推送的代理
[<UNUserNotificationCenterDelegate>]
iOS10收到通知不再是在
[application: didReceiveRemoteNotification:]
方法去处理, iOS10推出新的代理方法,接收和处理
各类通知(本地或者远程)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
//应用在前台收到通知 NSLog(@"========%@", notification);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
//点击通知进入应用 NSLog(@"response:%@", response);
}
UserNotifications(用户通知)
iOS 10 中将通知相关的 API 都统一了,苹果对这是做了重大改进,变的非常易用。
iOS 9 以前的通知
在调用方法时,有些方法让人很难区分,容易写错方法,这让开发者有时候很苦恼。
应用在运行时和非运行时捕获通知的路径还不一致。
应用在前台时,是无法直接显示远程通知,还需要进一步处理。
已经发出的通知是不能更新的,内容发出时是不能改变的,并且只有简单文本展示方式,扩展性根本不是很好。
iOS 10 开始的通知
所有相关通知被统一到了UserNotifications.framework框架中。
增加了撤销、更新、中途还可以修改通知的内容。
通知不在是简单的文本了,可以加入视频、图片,自定义通知的展示等等。
iOS 10相对之前的通知来说更加好用易于管理,并且进行了大规模优化,对于开发者来说是一件好事。
iOS 10开始对于权限问题进行了优化,申请权限就比较简单了(本地与远程通知集成在一个方法中)。
使用Xcode8之后,有些代码可能就编译不过去了,具体我就说说我碰到的问题。
1.UIWebView的代理方法:
**注意要删除NSError前面的 nullable,否则报错。
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error
{
[self hideHud];
}
使用Xcode8打开xib文件后,会出现下图的提示。
大家选择Choose Device即可。 之后大家会发现布局啊,frame乱了,只需要更新一下frame即可。如下图
注意:如果按上面的步骤操作后,在用Xcode7打开Xib会报一下错误,
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
这句话,以及把< document >中的toolsVersion和< plugIn >中的version改成你正常的xib文件中的值
,不过不建议这么做,在Xcode8出来后,希望大家都快速上手,全员更新。这就跟Xcode5到Xcode6一样,有变动,但是还是要尽早学习,尽快适应哟!
在iOS 10 中, UIRefreshControl可以直接在UICollectionView和UITableView中使用,并且脱离了UITableViewController.现在RefreshControl是UIScrollView的一个属性. 使用方法:
//创建
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
refreshControl.tintColor = [UIColor redColor];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"正在刷新"];
[refreshControl addTarget:self action:@selector(loadData) forControlEvents:UIControlEventValueChanged];
//开始和停止刷新
[refreshControl beginRefreshing];
[refreshControl endRefreshing];
也可以进去头文件查看
#import
- (instancetype)init;
@property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
@property (null_resettable, nonatomic, strong) UIColor *tintColor;
@property (nullable, nonatomic, strong) NSAttributedString *attributedTitle UI_APPEARANCE_SELECTOR;
// May be used to indicate to the refreshControl that an external event has initiated the refresh action
- (void)beginRefreshing NS_AVAILABLE_IOS(6_0);
// Must be explicitly called when the refreshing has completed
- (void)endRefreshing NS_AVAILABLE_IOS(6_0);
iOS 10 新增加的Pre-Fetching预加载
方法:
- (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray *)indexPaths NS_AVAILABLE_IOS(10_0);
- (void)collectionView:(UICollectionView *)collectionView cancelPrefetchingForItemsAtIndexPaths:(NSArray *)indexPaths NS_AVAILABLE_IOS(10_0);
注意:这个协议并不能代替之前读取数据的方法,仅仅是辅助加载数据.
Pre-Fetching预加载对UITableViewCell同样适用.
在iOS 10 中,UITextField新增了textContentType字段,是UITextContentType类型,它是一个枚举,作用是可以指定输入框的类型,以便系统可以分析出用户的语义.是电话类型就建议一些电话,是地址类型就建议一些地址.可以在#import 文件中,查看textContentType字段,有以下可以选择的类型:
UIKIT_EXTERN UITextContentType const UITextContentTypeName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeNamePrefix NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeGivenName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeMiddleName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeFamilyName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeNameSuffix NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeNickname NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeJobTitle NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeOrganizationName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeLocation NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeFullStreetAddress NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeStreetAddressLine1 NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeStreetAddressLine2 NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeAddressCity NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeAddressState NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeAddressCityAndState NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeSublocality NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeCountryName NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypePostalCode NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeTelephoneNumber NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeEmailAddress NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeURL NS_AVAILABLE_IOS(10_0);
UIKIT_EXTERN UITextContentType const UITextContentTypeCreditCardNumber NS_AVAILABLE_IOS(10_0);
在iOS10中,如果还使用以前设置UIStatusBar类型或者控制隐藏还是显示的方法,会报警告,方法过期
19970779-665271622c13eb6e
警告中提到从iOS9.0开始就弃用这两个方法了,需要用
-[UIViewController preferredStatusBarstyle]
-[UIViewController preferredStatusBarHidden]来替换使用,那我们来看看新的替换方法。
新技能见下面
#if UIKIT_DEFINE_AS_PROPERTIES
@property(nonatomic, readonly) UIStatusBarStyle preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
@property(nonatomic, readonly) BOOL prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
@property(nonatomic, readonly) UIStatusBarAnimation preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade
#else
- (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarStyleDefault
- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to NO
// Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; // Defaults to UIStatusBarAnimationFade
#endif
上面这个新方法在UIViewController.h文件中,这说明什么?当然说明这是viewController的属性和方法了,只需要在viewController里调用修改即可
UIStatusBarStyle 和 prefersStatusBarHidden这两个属性是readonly readonly readonly也就是说我们如果调用下面 肯定是报错的:
//这是错误的写法
self.preferredStatusBarStyle = UIStatusBarStyleDefault;和
self.prefersStatusBarHidden = YES;
正确的打开方式在viewController重写我们还没用的新的方法
//这是正确的
- (BOOL)prefersStatusBarHidden{
return YES;
}
- (UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleDefault;
}
Xcode8取消了三方插件的功能,好多教程破解可以继续使用,但是可能app上线可能会被拒。我们最喜爱的VVDocumenter-Xcode也不能使用了.
看来大神都是谦虚的啊(啥时候能成为大神。我还是洗洗睡吧,梦里啥都有\^_^)
上面也提到了我们可以继续使用注释,快捷键(⌥ Option + ⌘ Command + / )
真彩色的显示会根据光感应器来自动的调节达到特定环境下显示与性能的平衡效果,如果需要这个功能的话,可以在info.plist里配置(在Source Code模式下):
UIWhitePointAdaptivityStyle
它有五种取值,分别是:
UIWhitePointAdaptivityStyleStandard // 标准模式
UIWhitePointAdaptivityStyleReading // 阅读模式
UIWhitePointAdaptivityStylePhoto // 图片模式
UIWhitePointAdaptivityStyleVideo // 视频模式
UIWhitePointAdaptivityStyleStandard // 游戏模式
如果你的项目是游戏类的,就选择UIWhitePointAdaptivityStyleStandard这个模式,五种模式的显示效果是从上往下递减,也就是说如果你的项目是图片处理类的,你选择的是阅读模式,给选择太好的效果会影响性能.
官方文档中说:大多数core开头的图形框架和AVFoundation都提高了对扩展像素和宽色域色彩空间的支持.通过图形堆栈扩展这种方式比以往支持广色域的显示设备更加容易。现在对UIKit扩展可以在sRGB的色彩空间下工作,性能更好,也可以在更广泛的色域来搭配sRGB颜色.如果你的项目中是通过低级别的api自己实现图形处理的,建议使用sRGB,也就是说在项目中使用了RGB转化颜色的建议转换为使用sRGB,在UIColor类中新增了两个api:
+ (UIColor *)colorWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);
- (UIColor *)initWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);
我用新老方法测试两个方法在RGB相同的数值在表现上的区别看下图:
可以看出下面的颜色(sRGB方法)比上面的颜色(RGB方法)颜色更深更明显。
当系统版本到iOS10.0的时候 9.0和10.0比较的话是降序而不是升序,这样会导致iOS10.0是最早的版本,这样后面要走的iOS10的方法可能都不会走而出现问题
#define IOS9_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"9.0"] != NSOrderedAscending)
#define IOS8_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"8.0"] != NSOrderedAscending)
#define IOS7_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"7.0"] != NSOrderedAscending)
#define IOS6_OR_LATER ([[[UIDevice currentDevice] systemVersion] compare:@"6.0"] != NSOrderedAscending)
下面这样也不行它会永远返回NO,substringToIndex:1在iOS 10 会被检测成 iOS 1了,
#define isiOS10 ([[[[UIDevice currentDevice] systemVersion] substringToIndex:1] intValue]>=10)
1
#define isiOS10 ([[[[UIDevice currentDevice] systemVersion] substringToIndex:1] intValue]>=10)
正确的打开方式应该是:
#define IOS10_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0)
#define IOS9_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0)
#define IOS8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
#define IOS7_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
#define IOS6_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0)
这个问题实在没有找到好的方法解决。不过庆幸的是,公司决定将TabBar中的Item四个变成,既然好了,我就想不通。
如果你也遇到了这样的问题,或者已经解决了此问题,欢迎联系我,在此致谢!
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
总结了一下,2016年,这一年来所看的书和2017年计划所要看的书,后面的文章中会给出相关介绍并且说明我为什么会选择这些,同时以后也会时常回顾这些东西。
如果遗漏或者增加的后面会继续补充。
总结:
ios底层与高级相关
算法与数据结构相关
JavaScript权威指南
JavaScript高级程序设计
总结:
PHP后台与网站相关
JS->H5,微信小程序相关
我相信 : 我们每天不是在学习,就是在学习路上!
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
H.264是目前很流行的编码层视频压缩格式,目前项目中的协议层有rtmp与http,但是视频的编码层都是使用的H.264。 在熟悉H.264的过程中,为更好的了解H.264,尝试用VideoToolbox硬编码与硬解码H.264的原始码流。
1、网络提取层 (Network Abstraction Layer,NAL)
2、视讯编码层 (Video Coding Layer,VCL)
H.264由视讯编码层(Video Coding Layer,VCL)与网络提取层(Network Abstraction Layer,NAL)组成。 H.264包含一个内建的NAL网络协议适应层,藉由NAL来提供网络的状态,让VCL有更好的编译码弹性与纠错能力。
AVKit
AVFoundation
Video Toolbox
Core Media
Core Video
其中的AVKit和AVFoudation、VideoToolbox都是使用硬编码和硬解码。
VideoToolbox是iOS8以后开放的硬编码与硬解码的API,一组用C语言写的函数。使用流程如下:
1、-initVideoToolBox中调用VTCompressionSessionCreate创建编码session,然后调用VTSessionSetProperty设置参数,最后调用VTCompressionSessionPrepareToEncodeFrames开始编码;
2、开始视频录制,获取到摄像头的视频帧,传入-encode:,调用VTCompressionSessionEncodeFrame传入需要编码的视频帧,如果返回失败,调用VTCompressionSessionInvalidate销毁session,然后释放session;
3、每一帧视频编码完成后会调用预先设置的编码函数didCompressH264,如果是关键帧需要用CMSampleBufferGetFormatDescription获取CMFormatDescriptionRef,然后用
CMVideoFormatDescriptionGetH264ParameterSetAtIndex取得PPS和SPS;
最后把每一帧的所有NALU数据前四个字节变成0x00 00 00 01之后再写入文件;
4、调用VTCompressionSessionCompleteFrames完成编码,然后销毁session:VTCompressionSessionInvalidate,释放session。
创建session
int width = 480, height = 640;
OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &EncodingSession);
设置session属性
// 设置实时编码输出(避免延迟)
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
// 设置关键帧(GOPsize)间隔
int frameInterval = 10;
CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);
// 设置期望帧率
int fps = 10;
CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);
//设置码率,上限,单位是bps
int bitRate = width * height * 3 * 4 * 8;
CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
//设置码率,均值,单位是byte
int bitRateLimit = width * height * 3 * 4;
CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRateLimit);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
传入编码帧
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
// 帧时间,如果不设置会导致时间轴过长。
CMTime presentationTimeStamp = CMTimeMake(frameID++, 1000);
VTEncodeInfoFlags flags;
OSStatus statusCode = VTCompressionSessionEncodeFrame(EncodingSession,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
NULL, NULL, &flags);
关键帧获取SPS和PPS
bool keyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);
// 判断当前帧是否为关键帧
// 获取sps & pps数据
if (keyframe)
{
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
size_t sparameterSetSize, sparameterSetCount;
const uint8_t *sparameterSet;
OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0 );
if (statusCode == noErr)
{
// Found sps and now check for pps
size_t pparameterSetSize, pparameterSetCount;
const uint8_t *pparameterSet;
OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 );
if (statusCode == noErr)
{
// Found pps
NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
if (encoder)
{
[encoder gotSpsPps:sps pps:pps];
}
}
}
}
写入数据
CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length, totalLength;
char *dataPointer;
OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
if (statusCodeRet == noErr) {
size_t bufferOffset = 0;
static const int AVCCHeaderLength = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length
// 循环获取nalu数据
while (bufferOffset < totalLength - AVCCHeaderLength) {
uint32_t NALUnitLength = 0;
// Read the NAL unit length
memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
// 从大端转系统端
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
[encoder gotEncodedData:data isKeyFrame:keyframe];
// Move to the next NAL unit in the block buffer
bufferOffset += AVCCHeaderLength + NALUnitLength;
}
}
核心思路
用NSInputStream读入原始H.264码流,用CADisplayLink控制显示速率,用NALU的前四个字节识别SPS和PPS并存储,当读入IDR帧的时候初始化VideoToolbox,并开始同步解码;解码得到的CVPixelBufferRef会传入OpenGL ES类进行解析渲染。 具体细节 1、把原始码流包装成CMSampleBuffer
1、替换头字节长度;
uint32_t nalSize = (uint32_t)(packetSize - 4);
uint32_t *pNalSize = (uint32_t *)packetBuffer;
*pNalSize = CFSwapInt32HostToBig(nalSize);
2、用CMBlockBuffer把NALUnit包装起来;
CMBlockBufferRef blockBuffer = NULL;
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
(void*)packetBuffer, packetSize,
kCFAllocatorNull,
NULL, 0, packetSize,
0, &blockBuffer);
3、把SPS和PPS包装成CMVideoFormatDescription;
const uint8_t* parameterSetPointers[2] = {mSPS, mPPS};
const size_t parameterSetSizes[2] = {mSPSSize, mPPSSize};
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,
2, //param count
parameterSetPointers,
parameterSetSizes,
4, //nal start code size
&mFormatDescription);
4、添加CMTime时间;
(WWDC视频上说有,但是我在实现过程没有找到添加的地方,可能是我遗漏了)
5、创建CMSampleBuffer;
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {packetSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault,
blockBuffer,
mFormatDescription,
1, 0, NULL, 1, sampleSizeArray,
&sampleBuffer);
2、解码并显示
1、传入CMSampleBuffer
VTDecodeFrameFlags flags = 0;
VTDecodeInfoFlags flagOut = 0;
// 默认是同步操作。
// 调用didDecompress,返回后再回调
OSStatus decodeStatus = VTDecompressionSessionDecodeFrame(mDecodeSession,
sampleBuffer,
flags,
&outputPixelBuffer,
&flagOut);
2、回调didDecompress
void didDecompress(void *decompressionOutputRefCon, void *sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CVImageBufferRef pixelBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ){
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
*outputPixelBuffer = CVPixelBufferRetain(pixelBuffer);
}
3、显示解码的结果
[self.mOpenGLView displayPixelBuffer:pixelBuffer];
仔细对比硬编码和硬解码的图像,会发现硬编码的图像被水平镜像过。
当遇到IDR帧时,更合适的做法是通过
VTDecompressionSessionCanAcceptFormatDescription判断原来的session是否能接受新的SPS和PPS,如果不能再新建session。
AAC(Advanced Audio Coding),中文名:高级音频编码,出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、Sony等公司共同开发,目的是取代MP3格式。
AAC音频格式有ADIF和ADTS:
ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在磁盘文件中。
ADTS:Audio Data Transport Stream 音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。
AudioToolbox这个库是C的接口,偏向于底层,用于在线流媒体音乐的播放,可以调用该库的相关接口自己封装一个在线播放器类,AudioStreamer是老外封装的一个播放器类
• 数据类型
1.AudioFileStreamID 文件流
2.AudioQueueRef 播放队列
3.AudioStreamBasicDescription 格式化音频数据
4.AudioQueueBufferRef 数据缓冲
• 回调函数
1.AudioFileStream_PacketsProc 解析音频数据回调
2.AudioSessionInterruptionListener 音频会话被打断
3.AudioQueueOutputCallback 一个AudioQueueBufferRef播放完
• 主要函数
0.AudioSessionInitialize (NULL, NULL, AudioSessionInterruptionListener, self);
初始化音频会话
1.AudioFileStreamOpen(
(void*)self,
&AudioFileStreamPropertyListenerProc,
&AudioFileStreamPacketsProc,
0,
&audio_file_stream);
建立一个文件流AudioFileStreamID,传输解析的数据
2.AudioFileStreamParseBytes(
audio_file_stream,
datalen,
[data bytes],
kAudioFileStreamProperty_FileFormat);
解析音频数据
3.AudioQueueNewOutput(&audio_format, AudioQueueOutputCallback, (void*)self, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes, 0, &audio_queue);
创建音频队列AudioQueueRef
4.AudioQueueAllocateBuffer(queue, [data length], &buffer);
创建音频缓冲数据AudioQueueBufferRef
5.AudioQueueEnqueueBuffer(queue, buffer, num_packets, packet_descriptions);
把缓冲数据排队加入到AudioQueueRef等待播放
6.AudioQueueStart(audio_queue, nil); 播放
7.AudioQueueStop(audio_queue, true);
AudioQueuePause(audio_queue); 停止、暂停
• 断点续传
1。在http请求头中设置数据的请求范围,请求头中都是key-value成对
key:Range value:bytes=0-1000
[request setValue:range forHTTPHeaderField:@"Range"];
可以实现,a.网络断开后再连接能继续从原来的断点下载
b.可以实现播放进度可随便拉动
iOS上把PCM音频编码成AAC音频流
1、设置编码器(codec),并开始录制;
2、收集到PCM数据,传给编码器;
3、编码完成回调callback,写入文件。
具体步骤 1、创建并配置AVCaptureSession
创建AVCaptureSession,然后找到音频的AVCaptureDevice,根据音频device创建输入并添加到session,最后添加output到session。
audioFileHandle是NSFileHandle,用户写入编码后的AAC音频到文件。 demo中,此段代码还包括Video的设置。为了缩短篇幅,去掉了video相关的配置。
- (void)startCapture {
self.mCaptureSession = [[AVCaptureSession alloc] init];
mCaptureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
mEncodeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] lastObject];
self.mCaptureAudioDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:nil];
if ([self.mCaptureSession canAddInput:self.mCaptureAudioDeviceInput]) {
[self.mCaptureSession addInput:self.mCaptureAudioDeviceInput];
}
self.mCaptureAudioOutput = [[AVCaptureAudioDataOutput alloc] init];
if ([self.mCaptureSession canAddOutput:self.mCaptureAudioOutput]) {
[self.mCaptureSession addOutput:self.mCaptureAudioOutput];
}
[self.mCaptureAudioOutput setSampleBufferDelegate:self queue:mCaptureQueue];
NSString *audioFile = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"abc.aac"];
[[NSFileManager defaultManager] removeItemAtPath:audioFile error:nil];
[[NSFileManager defaultManager] createFileAtPath:audioFile contents:nil attributes:nil];
audioFileHandle = [NSFileHandle fileHandleForWritingAtPath:audioFile];
[self.mCaptureSession startRunning];
}
2、创建转换器
AudioStreamBasicDescription是输出流的结构体描述, 配置好outAudioStreamBasicDescription后, 根据AudioClassDescription(编码器), 调用AudioConverterNewSpecific创建转换器。
/**
* 设置编码参数
*
* @param sampleBuffer 音频
*/
- (void) setupEncoderFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
AudioStreamBasicDescription inAudioStreamBasicDescription = *CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)CMSampleBufferGetFormatDescription(sampleBuffer));
AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; // 初始化输出流的结构体描述为0. 很重要。
outAudioStreamBasicDescription.mSampleRate = inAudioStreamBasicDescription.mSampleRate; // 音频流,在正常播放情况下的帧率。如果是压缩的格式,这个属性表示解压缩后的帧率。帧率不能为0。
outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC; // 设置编码格式
outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_LC; // 无损编码 ,0表示没有
outAudioStreamBasicDescription.mBytesPerPacket = 0; // 每一个packet的音频数据大小。如果的动态大小,设置为0。动态大小的格式,需要用AudioStreamPacketDescription 来确定每个packet的大小。
outAudioStreamBasicDescription.mFramesPerPacket = 1024; // 每个packet的帧数。如果是未压缩的音频数据,值是1。动态帧率格式,这个值是一个较大的固定数字,比如说AAC的1024。如果是动态大小帧数(比如Ogg格式)设置为0。
outAudioStreamBasicDescription.mBytesPerFrame = 0; // 每帧的大小。每一帧的起始点到下一帧的起始点。如果是压缩格式,设置为0 。
outAudioStreamBasicDescription.mChannelsPerFrame = 1; // 声道数
outAudioStreamBasicDescription.mBitsPerChannel = 0; // 压缩格式设置为0
outAudioStreamBasicDescription.mReserved = 0; // 8字节对齐,填0.
AudioClassDescription *description = [self
getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC
fromManufacturer:kAppleSoftwareAudioCodecManufacturer]; //软编
OSStatus status = AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, description, &_audioConverter); // 创建转换器
if (status != 0) {
NSLog(@"setup converter: %d", (int)status);
}
}
获取编码器的方法
/**
* 获取编解码器
*
* @param type 编码格式
* @param manufacturer 软/硬编
*
编解码器(codec)指的是一个能够对一个信号或者一个数据流进行变换的设备或者程序。这里指的变换既包括将 信号或者数据流进行编码(通常是为了传输、存储或者加密)或者提取得到一个编码流的操作,也包括为了观察或者处理从这个编码流中恢复适合观察或操作的形式的操作。编解码器经常用在视频会议和流媒体等应用中。
* @return 指定编码器
*/
- (AudioClassDescription *)getAudioClassDescriptionWithType:(UInt32)type
fromManufacturer:(UInt32)manufacturer
{
static AudioClassDescription desc;
UInt32 encoderSpecifier = type;
OSStatus st;
UInt32 size;
st = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
sizeof(encoderSpecifier),
&encoderSpecifier,
&size);
if (st) {
NSLog(@"error getting audio format propery info: %d", (int)(st));
return nil;
}
unsigned int count = size / sizeof(AudioClassDescription);
AudioClassDescription descriptions[count];
st = AudioFormatGetProperty(kAudioFormatProperty_Encoders,
sizeof(encoderSpecifier),
&encoderSpecifier,
&size,
descriptions);
if (st) {
NSLog(@"error getting audio format propery: %d", (int)(st));
return nil;
}
for (unsigned int i = 0; i < count; i++) {
if ((type == descriptions[i].mSubType) &&
(manufacturer == descriptions[i].mManufacturer)) {
memcpy(&desc, &(descriptions[i]), sizeof(desc));
return &desc;
}
}
return nil;
}
3、获取到PCM数据并传入编码器
用CMSampleBufferGetDataBuffer获取到CMSampleBufferRef里面的CMBlockBufferRef,再通过CMBlockBufferGetDataPointer获取到pcmBufferSize和pcmBuffer; 调用AudioConverterFillComplexBuffer传入数据,并在callBack函数调用填充buffer的方法。
CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
CFRetain(blockBuffer);
OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
NSError *error = nil;
if (status != kCMBlockBufferNoErr) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
}
memset(_aacBuffer, 0, _aacBufferSize);
AudioBufferList outAudioBufferList = {0};
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = 1;
outAudioBufferList.mBuffers[0].mDataByteSize = (int)_aacBufferSize;
outAudioBufferList.mBuffers[0].mData = _aacBuffer;
AudioStreamPacketDescription *outPacketDescription = NULL;
UInt32 ioOutputDataPacketSize = 1;
// Converts data supplied by an input callback function, supporting non-interleaved and packetized formats.
// Produces a buffer list of output data from an AudioConverter. The supplied input callback function is called whenever necessary.
status = AudioConverterFillComplexBuffer(_audioConverter, inInputDataProc, (__bridge void *)(self), &ioOutputDataPacketSize, &outAudioBufferList, outPacketDescription);
Callback函数
/**
* A callback function that supplies audio data to convert. This callback is invoked repeatedly as the converter is ready for new input data.
*/
OSStatus inInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
AACEncoder *encoder = (__bridge AACEncoder *)(inUserData);
UInt32 requestedPackets = *ioNumberDataPackets;
size_t copiedSamples = [encoder copyPCMSamplesIntoBuffer:ioData];
if (copiedSamples < requestedPackets) {
//PCM 缓冲区还没满
*ioNumberDataPackets = 0;
return -1;
}
*ioNumberDataPackets = 1;
return noErr;
}
/**
* 填充PCM到缓冲区
*/
- (size_t) copyPCMSamplesIntoBuffer:(AudioBufferList*)ioData {
size_t originalBufferSize = _pcmBufferSize;
if (!originalBufferSize) {
return 0;
}
ioData->mBuffers[0].mData = _pcmBuffer;
ioData->mBuffers[0].mDataByteSize = (int)_pcmBufferSize;
_pcmBuffer = NULL;
_pcmBufferSize = 0;
return originalBufferSize;
}
4、得到rawAAC码流,添加ADTS头,并写入文件
AudioConverterFillComplexBuffer返回的是AAC原始码流,需要在AAC每帧添加ADTS头,调用adtsDataForPacketLength方法生成,最后把数据写入audioFileHandle的文件。
if (status == 0) {
NSData *rawAAC = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader];
[fullData appendData:rawAAC];
data = fullData;
} else {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
}
if (completionBlock) {
dispatch_async(_callbackQueue, ^{
completionBlock(data, error);
});
}
网上的ADTS头生成方法
/**
* Add ADTS header at the beginning of each and every AAC packet.
* This is needed as MediaCodec encoder generates a packet of raw
* AAC data.
*
* Note the packetLen must count in the ADTS header itself.
* See: http://wiki.multimedia.cx/index.php?title=ADTS
* Also: http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
**/
- (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
int adtsLength = 7;
char *packet = malloc(sizeof(char) * adtsLength);
// Variables Recycled by addADTStoPacket
int profile = 2; //AAC LC
//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
int freqIdx = 4; //44.1KHz
int chanCfg = 1; //MPEG-4 Audio Channel Configuration. 1 Channel front-center
NSUInteger fullLength = adtsLength + packetLength;
// fill in ADTS data
packet[0] = (char)0xFF; // 11111111 = syncword
packet[1] = (char)0xF9; // 1111 1 00 1 = syncword MPEG-2 Layer CRC
packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
packet[4] = (char)((fullLength&0x7FF) >> 3);
packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
packet[6] = (char)0xFC;
NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
return data;
}
在iOS设备上播放音频,可以使用AVAudioPlayer(AVFoundation框架内),但是不支持流式播放。
本文尝试两种播放方式:
使用AudioServicesPlaySystemSound(音频小于等于30s);
使用Audio Queue Services音频队列;
1、使用AudioServicesPlaySystemSound
AudioServicesCreateSystemSoundID创建系统声音
AudioServicesAddSystemSoundCompletion设置回调
AudioServicesPlaySystemSound开始播放
- (void)onClick:(UIButton *)button {
[self.mButton setHidden:YES];
NSURL *audioURL=[[NSBundle mainBundle] URLForResource:@"abc" withExtension:@"aac"];
SystemSoundID soundID;
//Creates a system sound object.
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(audioURL), &soundID);
//Registers a callback function that is invoked when a specified system sound finishes playing.
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, &playCallback, (__bridge void * _Nullable)(self));
// AudioServicesPlayAlertSound(soundID);
AudioServicesPlaySystemSound(soundID);
}
- (void)onPlayCallback {
[self.mButton setHidden:NO];
}
以下是API的限制
No longer than 30 seconds in duration
In linear PCM or IMA4 (IMA/ADPCM) format
Packaged in a .caf, .aif, or .wav file
虽然AAC音频不在支持列表里面,但是经过测试,播放是可以的。
2、使用Audio Queue Services音频队列
Audio Queue Services的播放步骤如下:
1,给buffer填充数据,并把buffer放入就绪的buffer queue;
2,应用通知队列开始播放;
3、队列播放第一个填充的buffer;
4、队列返回已经播放完毕的buffer,并开始播放下面一个填充好的buffer;
5、队列调用之前设置的回调函数,填充播放完毕的buffer;
6、回调函数中把buffer填充完毕,并放入buffer queue中。
问题1:malloc错误
malloc: *** error for object 0x154e58498: incorrect checksum for freed object - object was probably modified after being freed.
Set a breakpoint in malloc_error_break to debug.
问题2:selector调用错误
Method cache corrupted. This may be a message to an invalid object, or a memory error somewhere else.
objc[12730]: receiver 0x13fe1d4f0, SEL 0x10004e2d8, isa 0x100051828, cache 0x100051838, buckets 0x13fd86650, mask 0x7, occupied 0x1
objc[12730]: receiver 112 bytes, buckets 128 bytes
objc[12730]: selector 'fillBuffer:'
objc[12730]: isa 'AACPlayer'
objc[12730]: Method cache corrupted.
这两个问题是出现在AudioQueueAllocateBuffer方法和fillBuffer的调用,而且是时而正常,时而崩溃。 先查看参数是否正确,通过xcode的debug工具,我们可以看到以下的数据:
(AudioStreamBasicDescription) $0 = {
mSampleRate = 44100
mFormatID = 1633772320
mFormatFlags = 0
mBytesPerPacket = 0
mFramesPerPacket = 1024
mBytesPerFrame = 0
mChannelsPerFrame = 1
mBitsPerChannel = 0
mReserved = 0
}
maxSize = 768
packetNums = 85
(mStartOffset = 0, mVariableFramesInPacket = 0, mDataByteSize = 23)
AudioStreamBasicDescription的参数很熟悉,因为就是我们上一篇的编码所设置的参数。 AudioQueueAllocateBuffer的参数audioQueue、buffer_size、audioBuffers都很正常,暂时排除存在问题的可能性。
fillBuffer方法中,有AudioFileReadPackets和AudioQueueEnqueueBuffer两个方法。AudioQueueEnqueueBuffer是把buffer放入到AudioQueue,参数检查没有问题。初步判断是AudioFileReadPackets存在问题。
通过多次调试,发现AudioFileReadPackets在偶然情况下回返回-60的情况,这时会导致崩溃。 通过google查到-60对应的是kAudioFilePositionError,回来检查AudioFileReadPackets的参数,发现参数没有初始化,每次调用的参数都不同。 查API文档知道AudioFileReadPackets的参数除了audioFileID和cache、packet长度,均为传入参数,参数是否初始化并不会影响。至此,fillBuffer方法的线索断了。 回顾了一下整体的流程,决定从malloc错误入手,在so上找到以下解释。
you are freeing an object twice,
you are freeing a pointer that was never allocated
you are writing through an invalid pointer which previously pointed to an object which was already freed
内存访问越界,怎么会和selector调用错误扯上关系?百思不得其解。
最后,几经波折终于找到罪魁祸首。就是以下这行代码:
audioStreamPacketDescrption = malloc(sizeof(audioStreamPacketDescrption) * packetNums);
当我打过一次audioStreamPacketDescrption,再打AudioStreamPacketDescrption的时候,Xcode会自动索引为audioStreamPacketDescrption,导致sizeof会计算出不同的大小。
PS:按理说对一个结构体的类和结构体的实例进行sizeof,应该是一样的大小(不算动态分配)。
这个并没有错,可是为了方便我把audioStreamPacketDescrption定义成指针了!
两个教训:
1、不要起和类名一样的变量;
2、指针和实例的区别要从名字即可分清;
ffmpeg -i abc.h264 -i abc.aac -vcodec copy -f mp4 abc.mp4
用FFmpeg把H.264和AAC码流封装成mp4格式再打包成TS流,把生成的ts和m3u8文件放到Nginx的服务器目录下,用Safari访问对应的m3u8文件实现HLS的点播。
安装Homebrow->安装Nginx->安装FFmpeg->打包ts流并放入服务器==>直播和推流
配置Nginx以支持HLS的推流与拉流,iOS系统使用LFLiveKit推流,OS X系统使用FFmpeg推流,拉流端可以使用Safari浏览器或者VLC播放器。
配置Nginx,支持http协议拉流->配置Nginx,支持rtmp协议推流->重启Nginx->OS X系统推流->iOS系统推流->Safari浏览器拉流->VLC播放器拉流
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
由于之前一直使用MVC,而且在慢慢的开发中确实发现了不少关于MVC中存在的缺陷问题,前段时间试着使用MVVM在项目中去实战一下,发现缺点用着挺爽,他不想MVP那么复杂,也不会像MVC那么高耦合。
所以我打算以后再项目应用中尽量使用MVVM,这里只是简单的整理了一下代码!
M:Model中定义对应的模型属性
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *movieTime;
@property (nonatomic, copy) NSString *movieDetail;
@property (nonatomic, strong) NSURL *movieURL;
VM:ViewModel中定义对应的逻辑+数据+回调+代理方法
#import "iCocosModel.h"
typedef void(^resultSuccessBlock)(id resultData);
typedef void(^resultErrorBlock)(id resultError);
@interface iCocosViewModel : NSObject
- (void)getMoviesData;
- (void)moviesDetailWithPublicModel:(iCocosModel *)model withViewController:(UIViewController *)controller;
@property (nonatomic, copy) resultSuccessBlock successBlcok;
@property (nonatomic, copy) resultErrorBlock errorBlcok;
@end
实现对应的方法
#import "iCocosDetailController.h"
@implementation iCocosViewModel
- (void)getMoviesData
{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSString *url = @"https://api.douban.com/v2/movie/coming_soon";
[manager GET:url parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSArray *subjects = responseObject[@"subjects"];
NSMutableArray *modelArr = [NSMutableArray arrayWithCapacity:subjects.count];
for (NSDictionary *subject in subjects) {
iCocosModel *model = [[iCocosModel alloc] init];
model.movieName = subject[@"title"];
model.movieTime = subject[@"year"];
NSString *urlStr = subject[@"images"][@"medium"];
model.movieURL = [NSURL URLWithString:urlStr];
model.movieDetail = subject[@"alt"];
[modelArr addObject:model];
}
_successBlcok(modelArr);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
- (void)moviesDetailWithPublicModel:(iCocosModel *)model withViewController:(UIViewController *)controller
{
iCocosDetailController *detailVC = [[iCocosDetailController alloc] init];
detailVC.url = model.movieDetail;
[controller.navigationController pushViewController:detailVC animated:YES];
}
@end
V:View+Controller包括视图和控制器
View视图和MVC一样,用模型属性给控件赋值
#import "iCocosModel.h"
@interface iCocosViewCell : UITableViewCell
@property (nonatomic, strong) iCocosModel *model;
@end
View的实现
#import "iCocosViewCell.h"
#import <UIImageView+AFNetworking.h>
@interface iCocosViewCell ()
@property (nonatomic,strong) UILabel *nameLabel;
@property (nonatomic,strong) UILabel *yearLabel;
@property (nonatomic,strong) UIImageView *imgView;
@end
@implementation iCocosViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_imgView = [[UIImageView alloc] initWithFrame:CGRectMake(15, 10, 40, 60)];
[self.contentView addSubview:_imgView];
_nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(70, 10, 200, 20)];
[self.contentView addSubview:_nameLabel];
_yearLabel = [[UILabel alloc] initWithFrame:CGRectMake(70, 50, 100, 20)];
_yearLabel.textColor = [UIColor lightGrayColor];
_yearLabel.font = [UIFont systemFontOfSize:14];
[self.contentView addSubview:_yearLabel];
}
return self;
}
- (void)setModel:(iCocosModel *)model
{
_model = model;
_nameLabel.text = _model.movieName;
_yearLabel.text = _model.movieTime;
[_imgView setImage:model.movieURL];
}
@end
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
本系列文章引自一个朋友(讲师)的精华:袁峥Seemygo
直播难:个人认为要想把直播从零开始做出来,绝对是牛逼中的牛逼,大牛中的大牛,因为直播中运用到的技术难点非常之多,视频/音频处理,图形处理,视频/音频压缩,CDN分发,即时通讯等技术,每一个技术都够你学几年的。
直播易:已经有各个领域的大牛,封装好了许多牛逼的框架,我们只需要用别人写好的框架,就能快速的搭建一个直播app,也就是传说中的站在大牛肩膀上编程。
热门直播产品
映客,斗鱼,熊猫,虎牙,花椒等等 直播效果图
1.一个完整直播app功能(来自落影loyinglin分享)
1、聊天
私聊、聊天室、点亮、推送、黑名单等;
2、礼物
普通礼物、豪华礼物、红包、排行榜、第三方充值、内购、礼物动态更新、提现等;
3、直播列表
关注、热门、最新、分类直播用户列表等;
4、自己直播
录制、推流、解码、播放、美颜、心跳、后台切换、主播对管理员操作、管理员对用户等;
5、房间逻辑
创建房间、进入房间、退出房间、关闭房间、切换房间、房间管理员设置、房间用户列表等;
6、用户逻辑
普通登陆、第三方登陆、注册、搜索、修改个人信息、关注列表、粉丝列表、忘记密码、查看个人信息、收入榜、关注和取关、检索等;
7、观看直播
聊天信息、滚屏弹幕、礼物显示、加载界面等;
8、统计
APP业务统计、第三方统计等;
9、超管
禁播、隐藏、审核等;
2.一个完整直播app原理
直播原理:把主播录制的视频,推送到服务器,在由服务器分发给观众观看。
直播环节:推流端(采集、美颜处理、编码、推流)、服务端处理(转码、录制、截图、鉴黄)、播放器(拉流、解码、渲染)、互动系统(聊天室、礼物系统、赞) 3.一个完整直播app实现流程
1.采集、2.滤镜处理、3.编码、4.推流、5.CDN分发、6.拉流、7.解码、8.播放、9.聊天互动
4.一个完整直播app架构
5.一个完整直播app技术点
流媒体开发:网络层(socket或st)负责传输,协议层(rtmp或hls)负责网络打包,封装层(flv、ts)负责编解码数据的封装,编码层(h.264和aac)负责图像,音频压缩。
帧:每帧代表一幅静止的图像
GOP:(Group of Pictures)画面组,一个GOP就是一组连续的画面,每个画面都是一帧,一个GOP就是很多帧的集合
直播的数据,其实是一组图片,包括I帧、P帧、B帧,当用户第一次观看的时候,会寻找I帧,而播放器会到服务器寻找到最近的I帧反馈给用户。因此,GOP Cache增加了端到端延迟,因为它必须要拿到最近的I帧
GOP Cache的长度越长,画面质量越好
码率:图片进行压缩后每秒显示的数据量。
帧率:每秒显示的图片数。影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。
由于人类眼睛的特殊生理结构,如果所看画面之帧率高于16的时候,就会认为是连贯的,此现象称之为视觉暂留。并且当帧速达到一定数值后,再增长的话,人眼也不容易察觉到有明显的流畅度提升了。
分辨率:(矩形)图片的长度和宽度,即图片的尺寸
压缩前的每秒数据量:帧率X分辨率(单位应该是若干个字节)
压缩比:压缩前的每秒数据量/码率 (对于同一个视频源并采用同一种视频编码算法,则:压缩比越高,画面质量越差。)
视频文件格式:文件的后缀,比如.wmv,.mov,.mp4,.mp3,.avi,
主要用处,根据文件格式,系统会自动判断用什么软件打开,
注意: 随意修改文件格式,对文件的本身不会造成太大的影响,比如把avi改成mp4,文件还是avi.
视频封装格式:一种储存视频信息的容器,流式封装可以有TS、FLV等,索引式的封装有MP4,MOV,AVI等,
主要作用:一个视频文件往往会包含图像和音频,还有一些配置信息(如图像和音频的关联,如何解码它们等):这些内容需要按照一定的规则组织、封装起来.
注意:会发现封装格式跟文件格式一样,因为一般视频文件格式的后缀名即采用相应的视频封装格式的名称,所以视频文件格式就是视频封装格式。
视频封装格式和视频压缩编码标准:就好像项目工程和编程语言,封装格式就是一个项目的工程,视频编码方式就是编程语言,一个项目工程可以用不同语言开发。
1.1 采集视频、音频编码框架 *
AVFoundation:AVFoundation是用来播放和创建实时的视听媒体数据的框架,同时提供Objective-C接口来操作这些视听数据,比如编辑,旋转,重编码
1.2 视频、音频硬件设备 *
CCD:图像传感器: 用于图像采集和处理的过程,把图像转换成电信号。 拾音器:声音传感器: 用于声音采集和处理的过程,把声音转换成电信号。 音频采样数据:一般都是PCM格式 视频采样数据: 一般都是YUV,或RGB格式,采集到的原始音视频的体积是非常大的,需要经过压缩技术处理来提高传输效率
视频处理原理:因为视频最终也是通过GPU,一帧一帧渲染到屏幕上的,所以我们可以利用OpenGL ES,对视频帧进行各种加工,从而视频各种不同的效果,就好像一个水龙头流出的水,经过若干节管道,然后流向不同的目标
现在的各种美颜和视频添加特效的app都是利用GPUImage这个框架实现的,.
视频处理框架 *
GPUImage : GPUImage是一个基于OpenGL ES的一个强大的图像/视频处理框架,封装好了各种滤镜同时也可以编写自定义的滤镜,其本身内置了多达120多种常见的滤镜效果。 OpenGL:OpenGL(全写Open Graphics Library)是个定义了一个跨编程语言、跨平台的编程接口的规格,它用于三维图象(二维的亦可)。OpenGL是个专业的图形程序接口,是一个功能强大,调用方便的底层图形库。 OpenGL ES:OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。
3.1 视频编码框架 *
FFmpeg:是一个跨平台的开源视频框架,能实现如视频编码,解码,转码,串流,播放等丰富的功能。其支持的视频格式以及播放协议非常丰富,几乎包含了所有音视频编解码、封装格式以及播放协议。 -Libswresample:可以对音频进行重采样,rematrixing 以及转换采样格式等操 作。 -Libavcodec:提供了一个通用的编解码框架,包含了许多视频,音频,字幕流 等编码/解码器。 -Libavformat:用于对视频进行封装/解封装。 -Libavutil:包含一些共用的函数,如随机数生成,数据结构,数学运算等。 -Libpostproc:用于进行视频的一些后期处理。 -Libswscale:用于视频图像缩放,颜色空间转换等。 -Libavfilter:提供滤镜功能。 X264:把视频原数据YUV编码压缩成H.264格式 VideoToolbox:苹果自带的视频硬解码和硬编码API,但是在iOS8之后才开放。 AudioToolbox:苹果自带的音频硬解码和硬编码API
3.2 视频编码技术 *
视频压缩编码标准:对视频进行压缩(视频编码)或者解压缩(视频解码)的编码技术,比如MPEG,H.264,这些视频编码技术是压缩编码视频的
主要作用:是将视频像素数据压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。
注意:最影响视频质量的是其视频编码数据和音频编码数据,跟封装格式没有多大关系
MPEG:一种视频压缩方式,它采用了帧间压缩,仅存储连续帧之间有差别的地方 ,从而达到较大的压缩比
H.264/AVC:一种视频压缩方式,采用事先预测和与MPEG中的P-B帧一样的帧预测方法压缩,它可以根据需要产生适合网络情况传输的视频流,还有更高的压缩比,有更好的图象质量
注意1:如果是从单个画面清晰度比较,MPEG4有优势;从动作连贯性上的清晰度,H.264有优势
注意2:由于264的算法更加复杂,程序实现烦琐,运行它需要更多的处理器和内存资源。因此,运行264对系统要求是比较高的。
注意3:由于264的实现更加灵活,它把一些实现留给了厂商自己去实现,虽然这样给实现带来了很多好处,但是不同产品之间互通成了很大的问题,造成了通过A公司的编码器编出的数据,必须通过A公司的解码器去解这样尴尬的事情
H.265/HEVC:一种视频压缩方式,基于H.264,保留原来的某些技术,同时对一些相关的技术加以改进,以改善码流、编码质量、延时和算法复杂度之间的关系,达到最优化设置。
H.265 是一种更为高效的编码标准,能够在同等画质效果下将内容的体积压缩得更小,传输时更快更省带宽
I帧:(关键帧)保留一副完整的画面,解码时只需要本帧数据就可以完成(因为包含完整画面)
P帧:(差别帧)保留这一帧跟之前帧的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(P帧没有完整画面数据,只有与前一帧的画面差别的数据)
B帧:(双向差别帧)保留的是本帧与前后帧的差别,解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累
帧内(Intraframe)压缩:当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,帧内一般采用有损压缩算法
帧间(Interframe)压缩:时间压缩(Temporal compression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的
muxing(合成):将视频流、音频流甚至是字幕流封装到一个文件中(容器格式(FLV,TS)),作为一个信号进行传输。
3.3 音频编码技术 *
AAC,mp3:这些属于音频编码技术,压缩音频用
3.4码率控制 *
多码率:观众所处的网络情况是非常复杂的,有可能是WiFi,有可能4G、3G、甚至2G,那么怎么满足多方需求呢?多搞几条线路,根据当前网络环境自定义码率。 列如:常常看见视频播放软件中的1024,720,高清,标清,流畅等,指的就是各种码率。
3.5 视频封装格式 *
TS : 一种流媒体封装格式,流媒体封装有一个好处,就是不需要加载索引再播放,大大减少了首次载入的延迟,如果片子比较长,mp4文件的索引相当大,影响用户体验 为什么要用TS:这是因为两个TS片段可以无缝拼接,播放器能连续播放
FLV: 一种流媒体封装格式,由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,因此FLV格式成为了当今主流视频格式
librtmp:用来传输RTMP协议格式的数据
4.2 流媒体数据传输协议 *
RTMP:实时消息传输协议,Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的开放协议,因为是开放协议所以都可以使用了。 RTMP协议用于对象、视频、音频的传输。 这个协议建立在TCP协议或者轮询HTTP协议之上。 RTMP协议就像一个用来装数据包的容器,这些数据可以是FLV中的视音频数据。一个单一的连接可以通过不同的通道传输多路网络流,这些通道中的包都是按照固定大小的包传输的
chunk:消息包
5.1常用服务器 *
SRS:一款国人开发的优秀开源流媒体服务器系统 BMS:也是一款流媒体服务器系统,但不开源,是SRS的商业版,比SRS功能更多 nginx:免费开源web服务器,常用来配置流媒体服务器。
5.2数据分发 *
CDN:(Content Delivery Network),即内容分发网络,将网站的内容发布到最接近用户的网络”边缘”,使用户可以就近取得所需的内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度. CDN:代理服务器,相当于一个中介。 CDN工作原理:比如请求流媒体数据 1.上传流媒体数据到服务器(源站) 2.源站存储流媒体数据 3.客户端播放流媒体,向CDN请求编码后的流媒体数据 4.CDN的服务器响应请求,若节点上没有该流媒体数据存在,则向源站继续请求流媒体数据;若节点上已经缓存了该视频文件,则跳到第6步。 5.源站响应CDN的请求,将流媒体分发到相应的CDN节点上 6.CDN将流媒体数据发送到客户端 回源:当有用户访问某一个URL的时候,如果被解析到的那个CDN节点没有缓存响应的内容,或者是缓存已经到期,就会回源站去获取搜索。如果没有人访问,那么CDN节点不会主动去源站拿. 带宽:在固定的时间可传输的数据总量, 比如64位、800MHz的前端总线,它的数据传输率就等于64bit×800MHz÷8(Byte)=6.4GB/s 负载均衡: 由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助. 通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求。 均衡负载能够平均分配客户请求到服务器列阵,籍此提供快速获取重要数据,解决大量并发访问服务问题。 这种群集技术可以用最少的投资获得接近于大型主机的性能。 QoS(带宽管理):限制每一个组群的带宽,让有限的带宽发挥最大的效用
直播协议选择:
即时性要求较高或有互动需求的可以采用RTMP,RTSP
对于有回放或跨平台需求的,推荐使用HLS
直播协议对比 :
HLS:由Apple公司定义的用于实时流传输的协议,HLS基于HTTP协议实现,传输内容包括两部分,一是M3U8描述文件,二是TS媒体文件。可实现流媒体的直播和点播,主要应用在iOS系统
HLS是以点播的技术方式来实现直播
HLS是自适应码率流播,客户端会根据网络状况自动选择不同码率的视频流,条件允许的情况下使用高码率,网络繁忙的时候使用低码率,并且自动在二者间随意切
换。这对移动设备网络状况不稳定的情况下保障流畅播放非常有帮助。
实现方法是服务器端提供多码率视频流,并且在列表文件中注明,播放器根据播放进度和下载速度自动调整。
HLS与RTMP对比:HLS主要是延时比较大,RTMP主要优势在于延时低
HLS协议的小切片方式会生成大量的文件,存储或处理这些文件会造成大量资源浪费
相比使用RTSP协议的好处在于,一旦切分完成,之后的分发过程完全不需要额外使用任何专门软件,普通的网络服务器即可,大大降低了CDN边缘服务器的配置要求,可以使用任何现成的CDN,而一般服务器很少支持RTSP。
HTTP-FLV:基于HTTP协议流式的传输媒体内容。
相对于RTMP,HTTP更简单和广为人知,内容延迟同样可以做到1~3秒,打开速度更快,因为HTTP本身没有复杂的状态交互。所以从延迟角度来看,HTTP-FLV要优于RTMP。
RTSP:实时流传输协议,定义了一对多应用程序如何有效地通过IP网络传送多媒体数据.
RTP:实时传输协议,RTP是建立在UDP协议上的,常与RTCP一起使用,其本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于低层服务去实现这一过程。
RTCP:RTP的配套协议,主要功能是为RTP所提供的服务质量(QoS)提供反馈,收集相关媒体连接的统计信息,例如传输字节数,传输分组数,丢失分组数,单向和双向网络延迟等等。
7.1 解封装 *
demuxing(分离):从视频流、音频流,字幕流合成的文件(容器格式(FLV,TS))中, 分解出视频、音频或字幕,各自进行解码。
7.2 音频编码框架 *
fdk_aac:音频编码解码框架,PCM音频数据和AAC音频数据互转
7.3 解码介绍 *
硬解码:用GPU来解码,减少CPU运算 优点:播放流畅、低功耗,解码速度快, * 缺点:兼容不好 软解码:用CPU来解码 优点:兼容好 * 缺点:加大CPU负担,耗电增加、没有硬解码流畅,解码速度相对慢
ijkplayer:一个基于FFmpeg的开源Android/iOS视频播放器
API易于集成;
编译配置可裁剪,方便控制安装包大小;
支持硬件加速解码,更加省电
简单易用,指定拉流URL,自动解码播放.
IM:(InstantMessaging)即时通讯:是一个实时通信系统,允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流.
IM在直播系统中的主要作用是实现观众与主播、观众与观众之间的文字互动.
* 第三方SDK *
腾讯云:腾讯提供的即时通讯SDK,可作为直播的聊天室
融云:一个比较常用的即时通讯SDK,可作为直播的聊天室
七牛云:七牛直播云是专为直播平台打造的全球化直播流服务和一站式实现SDK端到端直播场景的企业级直播云服务平台.
网易视频云:基于专业的跨平台视频编解码技术和大规模视频内容分发网络,提供稳定流畅、低延时、高并发的实时音视频服务,可将视频直播无缝对接到自身App.
希望把我们的产品和它绑在一条船上,更加的依赖它。
技术生钱,帮养一大批牛B的程序员
第三方SDK开发: 对于一个初创团队来讲,自研直播不管在技术门槛、CDN、带宽上都是有很大的门槛的,而且需要耗费大量的时间才能做出成品,不利于拉投资。
自研:公司直播平台大,从长远看,自研可以节省成本,技术成面比直接用SDK可控多了。
降低成本
使用好的第三方企业服务,将不用再花高价请猎头去挖昂贵的大牛,也不用去安抚大牛们个性化的脾气
提升效率
第三方服务的专注与代码集成所带来的方便,所花费的时间可能仅仅是1-2个小时,节约近99%的时间,足够换取更多的时间去和竞争对手斗智斗勇,增加更大的成功可能性
降低风险
借助专业的第三方服务,由于它的快速、专业、稳定等特点,能够极大地加强产品的竞争能力(优质服务、研发速度等),缩短试错时间,必将是创业中保命的手段之一
专业的事,找专业的人来做
第三方服务最少是10-20人的团队专注地解决同一个问题,做同一件事情。第三方服务所带来的支持效果,绝不是通过1-2个人处理所能对比的,难道不是吗
结束语
后续还会有讲解视频采集,美颜,聊天室,礼物系统等更多功能,敬请关注!!!
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
网上讨论比较多并且支持Android/iOS的项目
Vitamio
IJKPlayer
首先说下Vitamio目前可以拿到的版本是4.20,商业使用需要付费。
这里只介绍IJKPlayer,为什么?用了你就知道了!
ijkplayer 是一款做视频直播的框架, 基于ffmpeg, 支持 Android 和 iOS, 网上也有很多集成说明, 但是个人觉得还是不够详细, 在这里详细的讲一下在 iOS 中如何集成ijkplayer, 即便以前从没有接触过, 按着下面做也可以集成成功!
必备条件:
# install homebrew, git, yasm
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install git
brew install yasm
下载完成后解压, 解压后文件夹内部目录如下图:
说是编译 ijkplayer, 其实是编译 ffmpeg, 在这里我们已经下载好了ijkplayer, 所以 github 上README.md中的Build iOS那一步中有一些步骤是不需要的.
下面开始一步一步编译:
2. 执行命令行./init-ios.sh, 这一步是去下载 ffmpeg 的, 时间会久一点, 耐心等一下.如下图:
3. 在第2步中下载完成后, 执行cd ios, 也就是进入到 ios目录中, 如下图:
4. 进入 ios 文件夹后, 在终端依次执行./compile-ffmpeg.sh clean和./compile-ffmpeg.sh all命令, 编译 ffmpeg, 也就是README.md中这两步, 如下图:
编译时间较久, 耐心等待一下.
./init-ios.sh
cd ios
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
集成 ijkplayer 有两种方法: 一种方法是按照IJKMediaDemo工程中那样, 直接导入工程IJKMediaPlayer.xcodeproj, 在这里不做介绍, 如下图:
第二种集成方法是把 ijkplayer 打包成framework导入工程中使用. 下面开始介绍如何打包IJKMediaFramework.framework, 按下面步骤开始一步一步做:
打开后是这样的, 如下图:
2. 工程打开后设置工程的 scheme, 具体步骤如下图:
3. 设置好 scheme 后, 分别选择真机和模拟器进行编译, 编译完成后, 进入 Finder, 如下图:
进入 Finder 后, 可以看到有真机和模拟器两个版本的编译结果, 如下图:
下面开始合并真机和模拟器版本的 framework, 注意不要合并错了, 合并的是这个文件, 如下图:
打开终端, 进行合并, 命令行具体格式为:
lipo -create “真机版本路径” “模拟器版本路径” -output “合并后的文件路径”
合并后如下图:
下面很重要, 需要用合并后的IJKMediaFramework把原来的IJKMediaFramework替换掉, 如下图, 希望你能看懂:
上图中的1、2两步完成后, 绿色框住的那个IJKMediaFramework.framework文件就是我们需要的框架了, 可以复制出来, 稍后我们需要导入工程使用.
新建工程, 导入合并后的IJKMediaFramework.framework以及相关依赖框架以及相关依赖框架,如下图:
导入框架后, 在ViewController.m进行测试, 首先导入IJKMediaFramework.h头文件, 编译看有没有错, 如果没有错说明集成成功.
接着开始在ViewController.m文件中使用IJKMediaFramework框架进行测试使用, 写一个简单的直播视频进行测试, 在这里看一下运行后的结果, 后面会放上 Demo 供下载.
为苦于各种奇怪原因而无法玩耍的小伙伴们提供了包装了ijkplayer的pod,仅供测试体验。
1.基于ijkplayer 5737ccc提交制作成的framework,需要注意的是需要iOS8+。
2.如果使用ijkplayer过程中遇到BUG什么的,可以移步去ijkplayer作者的GitHub上提issue或者PR。
哦对了,地址在这里https://coding.net/u/shirokuma/p/IJKMediaLibrary/git,因framework超过100MB无法传到GitHub上,就放到Coding上了。祝各位玩的愉快!
项目源码:(在集成或者使用之前请细细品读,也许你会发现不一样的乐趣)
//
// ViewController.m
// iCocosIjkPlayer
//
// Created by tqy on 16/8/8.
// Copyright © 2016年 iCocos. All rights reserved.
//
#import "ViewController.h"
#import <IJKMediaFramework/IJKMediaFramework.h>
@interface ViewController ()
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, retain) id<IJKMediaPlayback> player;
@property (nonatomic, weak) UIView *PlayerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//网络视频
// self.url = [NSURL URLWithString:@"https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"];
// _player = [[IJKAVMoviePlayerController alloc] initWithContentURL:self.url];
//直播视频
self.url = [NSURL URLWithString:@"http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8"];
_player = [[IJKFFMoviePlayerController alloc] initWithContentURL:self.url withOptions:nil];
UIView *playerView = [self.player view];
UIView *displayView = [[UIView alloc] initWithFrame:CGRectMake(0, 50, self.view.bounds.size.width, 180)];
self.PlayerView = displayView;
self.PlayerView.backgroundColor = [UIColor blackColor];
[self.view addSubview:self.PlayerView];
playerView.frame = self.PlayerView.bounds;
playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.PlayerView insertSubview:playerView atIndex:1];
[_player setScalingMode:IJKMPMovieScalingModeAspectFill];
[self installMovieNotificationObservers];
}
-(void)viewWillAppear:(BOOL)animated{
if (![self.player isPlaying]) {
[self.player prepareToPlay];
}
}
#pragma Selector func
- (void)loadStateDidChange:(NSNotification*)notification {
IJKMPMovieLoadState loadState = _player.loadState;
if ((loadState & IJKMPMovieLoadStatePlaythroughOK) != 0) {
NSLog(@"LoadStateDidChange: IJKMovieLoadStatePlayThroughOK: %d\n",(int)loadState);
}else if ((loadState & IJKMPMovieLoadStateStalled) != 0) {
NSLog(@"loadStateDidChange: IJKMPMovieLoadStateStalled: %d\n", (int)loadState);
} else {
NSLog(@"loadStateDidChange: ???: %d\n", (int)loadState);
}
}
- (void)moviePlayBackFinish:(NSNotification*)notification {
int reason =[[[notification userInfo] valueForKey:IJKMPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue];
switch (reason) {
case IJKMPMovieFinishReasonPlaybackEnded:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackEnded: %d\n", reason);
break;
case IJKMPMovieFinishReasonUserExited:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonUserExited: %d\n", reason);
break;
case IJKMPMovieFinishReasonPlaybackError:
NSLog(@"playbackStateDidChange: IJKMPMovieFinishReasonPlaybackError: %d\n", reason);
break;
default:
NSLog(@"playbackPlayBackDidFinish: ???: %d\n", reason);
break;
}
}
- (void)mediaIsPreparedToPlayDidChange:(NSNotification*)notification {
NSLog(@"mediaIsPrepareToPlayDidChange\n");
}
- (void)moviePlayBackStateDidChange:(NSNotification*)notification {
switch (_player.playbackState) {
case IJKMPMoviePlaybackStateStopped:
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: stoped", (int)_player.playbackState);
break;
case IJKMPMoviePlaybackStatePlaying:
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: playing", (int)_player.playbackState);
break;
case IJKMPMoviePlaybackStatePaused:
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: paused", (int)_player.playbackState);
break;
case IJKMPMoviePlaybackStateInterrupted:
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: interrupted", (int)_player.playbackState);
break;
case IJKMPMoviePlaybackStateSeekingForward:
case IJKMPMoviePlaybackStateSeekingBackward: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: seeking", (int)_player.playbackState);
break;
}
default: {
NSLog(@"IJKMPMoviePlayBackStateDidChange %d: unknown", (int)_player.playbackState);
break;
}
}
}
#pragma Install Notifiacation
- (void)installMovieNotificationObservers {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loadStateDidChange:)
name:IJKMPMoviePlayerLoadStateDidChangeNotification
object:_player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayBackFinish:)
name:IJKMPMoviePlayerPlaybackDidFinishNotification
object:_player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mediaIsPreparedToPlayDidChange:)
name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
object:_player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayBackStateDidChange:)
name:IJKMPMoviePlayerPlaybackStateDidChangeNotification
object:_player];
}
- (void)removeMovieNotificationObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IJKMPMoviePlayerLoadStateDidChangeNotification
object:_player];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IJKMPMoviePlayerPlaybackDidFinishNotification
object:_player];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification
object:_player];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:IJKMPMoviePlayerPlaybackStateDidChangeNotification
object:_player];
}
- (IBAction)play_btn:(id)sender {
if (![self.player isPlaying]) {
[self.player play];
}else{
[self.player pause];
}
}
@end
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1
H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示。
其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。
H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。本文的程序即实现了上述的两个步骤。
ACC原理
AAC原始码流(又称为“裸流”)是由一个一个的ADTS frame组成的。他们的结构如下图所示。
其中每个ADTS frame之间通过syncword(同步字)进行分隔。同步字为0xFFF(二进制“111111111111”)。AAC码流解析的步骤就是首先从码流中搜索0x0FFF,分离出ADTS frame;然后再分析ADTS frame的首部各个字段。本文的程序即实现了上述的两个步骤。
FLV原理
FLV封装格式是由一个FLV Header文件头和一个一个的Tag组成的。Tag中包含了音频数据以及视频数据。FLV的结构如下图所示。
有关FLV的格式本文不再做记录。可以参考文章《视音频编解码学习工程:FLV封装格式分析器》。本文的程序实现了FLV中的FLV Header和Tag的解析,并可以分离出其中的音频流。
clpaial10201119(Q Q:2211523682)
http://weibo.com/u/3288975567?is_hot=1