ios中iPhoneXios 导航栏不透明为什么有一小段透明

博主最新文章
博主热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)iphonex变无脸男app|iphonex变无脸男软件1.0 ios最新版-东坡下载
东坡下载:内容最丰富最安全的下载站!
您的位置:
→ iphonex变无脸男软件 1.0 ios最新版
更多安卓版
扫描安装到手机
iphonex变无脸男软件是一款非常有趣味的相机类型的软件,这个软件可以直接的让人脸变为透明,并且使用起来也是非常的方便,软件是基于动画制作,就像视频之感!iPhonex无脸男视频转变 可以让人物脸部透明iphonex变无脸男软件特点1、可以自定义的选择样式2、透明的部分也是可以自由选择3、完全免费脸部透明软件相关新闻这个日本游戏开发人利用unity和iPhonex所制作的一款工具,实现了动画中没有脸的效果,这个功能其实也是iPhone自带的faceid自动的功能,现在这个软件还属于开发阶段!暂时没有上线!这个软件的ar功能其实也是光学伪装功能,其内容也是特别的!
iphonex变无脸男软件下载地址
扫描二维码,安装到手机&figure&&img src=&https://pic2.zhimg.com/v2-2702b45aec5db8f_b.jpg& data-rawwidth=&1800& data-rawheight=&1000& class=&origin_image zh-lightbox-thumb& width=&1800& data-original=&https://pic2.zhimg.com/v2-2702b45aec5db8f_r.jpg&&&/figure&&p&上周在微信群中有位同学提出了一个疑问,对于购物车的设计差异性究竟在哪里?是的,相较于其他的功能界面来说,购物车界面设计的确有些趋同。但这并不意味着没有研究它的意义,相反它可能会更加的重要。&/p&&p&只要是电商类产品,购物车这个功能一定是绕不开的,它可以说是电商类产品设计的标配功能。作为平台交易转化最重要的环节之一,每一个细节的调整可能都会带来数据的波动。&/p&&p&因此更需要我们对其有深入的思考、小心的求证。今天关于购物车的设计,我们从一个关于它的小故事开始。&/p&&h2&&b&Cart VS Bag&/b&&/h2&&p&如果大家经常逛各类电商平台,应该发现对于购物车这个概念有些平台使用 Cart(购物车),而有些使用 Bag(购物袋)。&/p&&p&抛掉一些特殊案例,我们基本可以发现通常只有时尚、化妆品、配饰类的垂直电商平台会使用 Bag,比如 Zara 的独立客户端。&/p&&p&那么使用 Cart 和 Bag 各自会有什么差异呢?会对交易产生影响吗?美国有一个设计公司(Conversion Fanatics)在 2 年前借一次为客户进行设计改版的机会对这个问题进行了一次研究。&/p&&p&在改版过程中他们分别使用 Add to cart 和 Add to bag 进行了一次测试。结果令人惊讶,50:50 的流量分发测试下使用 Add to cart 对支付界面带来的 PV 比使用 Add to bag 居然高了 95.3% ,而其他各项数据也有非常明显的提升。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-6d4fbc751fe64ba31506c7d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1388& data-rawheight=&296& class=&origin_image zh-lightbox-thumb& width=&1388& data-original=&https://pic2.zhimg.com/v2-6d4fbc751fe64ba31506c7d_r.jpg&&&/figure&&p&我们在前面提到过,购物车是电商平台交易转化最重要的环节之一,转化率的微弱提升都将会为平台带来巨大的利益。那么这些垂直电商平台为什么从一开始都选择了 Bag 来代替购物车呢?&/p&&p&Uxmovement 曾经有一篇文章探讨过这个问题。他们认为用户对 Bag 会有很多种不同的理解,如果用户是在一个卖包的平台上购物,他们认为 Add to bag 会让他们购买另外一个他们并不需要配饰。&/p&&p&而从心理层面来说,Cart 是一个帮忙用户收集想要购买的商品的容器,用户可以很随意的将商品放入或拿出,没有任何压力。而 Bag 会让用户觉得这些东西已经购买了,不能再改变主意了。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f28e33adfb88_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1164& data-rawheight=&358& class=&origin_image zh-lightbox-thumb& width=&1164& data-original=&https://pic1.zhimg.com/v2-f28e33adfb88_r.jpg&&&/figure&&p&我们再回到前面测试中的对比数据,这也许这正好是一个特殊个案、也许这个数据样本量还不够大。但它其实给了我们一些很重要的信息:&/p&&ol&&li&用户对信息的理解往往有很多是我们并没有认知到的;&/li&&li&即使是很小的改动,也会对平台的整体漏斗带来很大的影响;&/li&&li&对于转化的核心链路的设计改变,我们是需要慎重的。&/li&&/ol&&p&关于 Cart 和 Bag,Conversion Fanatics 并没有给出后续更多的跟进信息,更多的是抛出给大家一个去观察、理解用户的角度、观点。&/p&&p&用户对购物车的理解是什么?它为平台传递出哪些信息?如何去获得更大层面的成功?都是我们在购物车这个功能界面设计过程中需要去思考的问题。带着这些信息,我们来聊聊购物车的设计逻辑。&/p&&h2&&b&购物车解决什么问题&/b&&/h2&&p&在前面 Pattern 划分的时候,购物车被归入到了业务类型。这也就是说它的设计是分层的,除了基础(default)的设计之外,我们还需要叠加上一层业务的特性,它最终的呈现也与业务有着很强的关系。&/p&&p&总的来说购物车是平台用于促进用户购买的一个功能模块。表面上来看它是用来暂存用户想要购买的商品,但更抽象一步来看它是用来桥接用户“逛”与“买”之间的间隙。而在这个间隙中,我们需要从最基础的功能角度出发,一步步的去放大购物车的价值。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2b5e3b90b648cd76f589_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic2.zhimg.com/v2-2b5e3b90b648cd76f589_r.jpg&&&/figure&&p&上图中截取了 4 个不同平台的购物车界面。我们可以看到从左至右购物车界面的信息、功能的复杂度在一步步在递增。&/p&&p&如果我们仅仅从纯表现层面来看,有很多的设计我们很难去解释为什么需要?为什么要这么做?所以,我们需要先将购物车的设计做一个拆分,一部分是购物车这个模式所必须的,一部分是因为业务特性所附加的。&/p&&h2&&b&购物车基本框架&/b&&/h2&&p&我们前面说过,对于一个有着明确定义、用户认知的功能模块,它的设计是需要具备一定的普适性的。也只有在这个基础之上我们才能先维持其对整个交易链路的基础保障。毕竟理念上过于超前的设计大部分用户可能都不会使用,这也将于我们的设计目标、业务目标相悖。&/p&&p&我们先将业务的部分抛开,一个最基础、常见的购物车界面可能会是如下图(Fancy)的样子。主要是对商品信息、价格、总价的展示以及 CTA。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-21e2bd44eac51d3f48f7c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic1.zhimg.com/v2-21e2bd44eac51d3f48f7c_r.jpg&&&/figure&&p&如果我们在扩大一下范围,多找来几个不同产品的购物车界面叠加在一起拉一个透明度,你会发现一些每个购物车界面都会有的共同信息。比如商品标题、图片、数量、单价、总价、CTA…&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d50db5be2cd072bd7de5de4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic1.zhimg.com/v2-d50db5be2cd072bd7de5de4_r.jpg&&&/figure&&p&因此我们可以通过这些“同类项”获得购物车界面的一个基础界面框架,也是购物车界面的基础配备。&/p&&p&用户们日常所接触到的购物车基本都包含这些元素、这样布局,当我们为一款新的产品设计购物车界面时,它应该成为我们开始的基础,以确保用户最小的学习成本、更高的效率。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-c729f00d5c3eaa7de6722bb26aeaf282_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic3.zhimg.com/v2-c729f00d5c3eaa7de6722bb26aeaf282_r.jpg&&&/figure&&p&从这个基础框架来看,购物车最基础需要解决的问题是告知用户想要购买的商品有哪些、什么规格、数量多少,以及整体的总价并引导进行支付操作。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-4b4e6573cceb72dd25e2448d_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1600& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1600& data-original=&https://pic2.zhimg.com/v2-4b4e6573cceb72dd25e2448d_r.jpg&&&/figure&&p&满足了购物车界面的基础框架之后,它基本上就能够对用户在在线购物平台上进行购物的进行最基础的支持,顺利的完成购买行为。&/p&&p&但作为整个交易路径最为重要的环节之一,我们还需要通过设计将它的转化率进行不断的放大。这也就意味着我们需要对购物车界面进行一些“装饰”,让用户的购买意愿度、购买数量进一步的提升。&/p&&hr&&p&以上是 Design System 系列的第 11 期的节选内容,在接下来的全文内容中(付费部分)我们将从三个不同层面,通过几个实际案例的分析来进行购物车界面的进一步的设计拆解。&/p&&p&加入 PinDesign 会员,获取本期主题「&b&购物车的设计逻辑&/b&」的全文内容及本系列前 2 期周刊的赠送,年付会员将获得 Design System 系列所有(01~10)文章的赠送。&/p&&p&&b&Design System&/b& 是 PinDesign 周刊的一个新系列,基于「Design Systems」的理念对产品的系统性设计的经验总结。希望将自己的感受和经验分享给大家,辅助大家的阅读。&/p&&p&&a href=&http://link.zhihu.com/?target=https%3A//h5.youzan.com/v2/showcase/promocode/fetch%3Falias%3D1h54ny8m6& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&点击领取 PinDesign 会员计划 50 元优惠券&/a&&/p&&p&&b&Design System 系列已更新:&/b&&/p&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/v2-c83c02ef56c9c8c49bf725_180x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:什么是 Design System&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-0ab350810afc95d17cd.jpg& data-image-width=&900& data-image-height=&500& class=&internal&&5key:Design Systems 02 - 什么是 Design Principles&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/v2-894bf47b1cf360b94db2ded_180x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:Components 与 Patterns 究竟有什么区别&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-db9adcfc4de2c_180x120.jpg& data-image-width=&900& data-image-height=&500& class=&internal&&5key:你该为产品设计怎样的气质&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-c58b4bc032c875dx120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:Design System 中的 Design Token&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic2.zhimg.com/v2-4beb385fa6cc_180x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:Design Pattern 实例 - 用户通知与中断&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic3.zhimg.com/v2-fa6a4aaebe79ce2_180x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:Design Pattern 组合实例 - 列表页设计思考&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic4.zhimg.com/v2-459a9d2af3a9fd8563bca8f0x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:Design Pattern 划分方式是对设计的逻辑思考&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic4.zhimg.com/v2-274f9fd4fcd9d66a32bc3_180x120.jpg& data-image-width=&2000& data-image-height=&1112& class=&internal&&5key:Design Pattern - 页面的信息展示逻辑&/a&&a href=&https://zhuanlan.zhihu.com/p/& data-draft-node=&block& data-draft-type=&link-card& data-image=&https://pic1.zhimg.com/v2-11b8d23ab89f5bcab29d6b1d_180x120.jpg& data-image-width=&1800& data-image-height=&1000& class=&internal&&5key:换个角度谈表单设计&/a&&p&&b&点击下方链接,了解 PinDesign 会员计划详细信息:&/b&&/p&&a href=&http://link.zhihu.com/?target=https%3A//wap.youzan.com/v2/showcase/goods%3Falias%3Dfujcrfun& data-draft-node=&block& data-draft-type=&link-card& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&PinDesign 互联网产品设计周刊&/a&&p&&/p&&p&&/p&&p&&/p&&p&&/p&
上周在微信群中有位同学提出了一个疑问,对于购物车的设计差异性究竟在哪里?是的,相较于其他的功能界面来说,购物车界面设计的确有些趋同。但这并不意味着没有研究它的意义,相反它可能会更加的重要。只要是电商类产品,购物车这个功能一定是绕不开的,它…
&p&谢几百个邀。这题确实很大,很难讲得面面俱到,我便暂且从我个人经验和角度出发,希望能够起到抛砖引玉的效果,给诸位一些小小的建议。&/p&&p&本文共计:4401字,预计阅读时间:7分钟&/p&&p&目录(可按需进行定位阅读)&/p&&ol&&li&前言:明确编程的本质&/li&&li&必要的辅助工具与技能&/li&&li&明确学习的方向&/li&&li&选择合适的学习方式与方法&/li&&li&总结&/li&&/ol&&h2&&b&0)前言:明确编程的本质&/b&&/h2&&p&其实我们在小学时代解答应用题的时候,就已经逐步培养起将实际问题抽象建模、选择合适算法(例如加减乘除)进行求解的思维方式。当计算量巨大或者问题过于繁冗时,人工手算显然开始力不所能及,现代计算机便应运而生,自动化编程也成为了一种社会发展的必然趋势。&/p&&p&所以究起根本,编程即是将实际问题抽象为一种模型,再用一种高度逻辑化的语言,用特定的算法进行描述与实现。&/p&&h2&1)必要的辅助工具与技能&/h2&&p&&b&学会使用git指令和github:&/b&&/p&&p&我大四时前往爱尔兰进行交流学习,教授ruby语言的老师专门用了整整一个学期,反复穿插地考察了我们git的掌握情况。刚接触的时候苦不堪言,常常一条错误的命令敲下去,写了三天的代码就能当场从爱尔兰回滚到中国。有好几次我都是在deadline前出了branch merge的错——&/p&&p&不如连夜客死他乡。&/p&&p&但当我逐渐掌握git指令并学会使用github后,我才意识到github是多么牛逼的存在。作为全球最大的同性交友平台,github的版本控制功能和代码托管功能十分完善。特别是在实际工作场景里,一个多人的大型项目如何规避冲突和高效配合将成为首要重点。&/p&&p&对于初学者,学会如何使用github将会提供了一个良好的开端。养成不同功能的模块开发写在不同的branch上的习惯,能够使你的项目逻辑清晰并易于修改。如果你把所有的代码全部放在一起,一旦你写了层层嵌套的塔型狗屎(这是你一定逃脱不了的命运……),无法连根撤回的bug堆将会直接击碎你还没开始就已结束的编程梦想。&/p&&p&所以如果你想要学习编程,那么请现在就打开一个新的tab页,输入&a href=&//link.zhihu.com/?target=https%3A//github.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com&/span&&span class=&invisible&&&/span&&/a&,注册属于你的交友账号。初学的小朋友们也不要过于担心。这玩意儿虽然看起来有些复杂,但是相信我,它比看起来更特么复杂……不过不要畏难,要知道这点困难在你今后的编程生涯里,毛都不算……&/p&&p&相关教程:&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//guides.github.com/activities/hello-world/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&github官方教程&/a&:&a href=&//link.zhihu.com/?target=https%3A//guides.github.com/activities/hello-world/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&guides.github.com/activ&/span&&span class=&invisible&&ities/hello-world/&/span&&span class=&ellipsis&&&/span&&/a&;&/p&&p&个人博客:&a href=&//link.zhihu.com/?target=https%3A//github.com/phodal/github& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&github.com/phodal/githu&/span&&span class=&invisible&&b&/span&&span class=&ellipsis&&&/span&&/a&;&/p&&p&菜鸟教程:&a href=&//link.zhihu.com/?target=http%3A//www.runoob.com/git/git-tutorial.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&runoob.com/git/git-tuto&/span&&span class=&invisible&&rial.html&/span&&span class=&ellipsis&&&/span&&/a&;&/p&&p&&b&选择一款合适的IDE:&/b&&/p&&p&挑选一款合适的IDE会使编程体验和效率大大提升。IDE即集成开发环境,是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和用户图形界面。不同的IDE可以适配不同的工作应用场景,例如:PHP:PhpStorm;web开发:WebStorm;Java:Eclipse,Intellij IDEA;C、C++:Visual Studio ,Xcode;扩展性强的通用IDE:Sublime 2,Emacs,Vim;&/p&&p&建议右上角关闭:word和txt文本编辑器&/p&&p&sublime 2最易上手,虽然它是收费软件,但却可无限期试用……可谓良心代表 &/p&&p&sublime 2下载地址:&a href=&//link.zhihu.com/?target=http%3A//www.sublimetext.com/2& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&sublimetext.com/2&/span&&span class=&invisible&&&/span&&/a&;&/p&&p&vim不建议新手使用,因为如果不明白操作指令,进入vim模式后你只能茫然地敲下若干随机字符串,然后关机以求解脱。。。&/p&&p&我曾偶然看过一部电视剧的片段,女主在一夜之间学会了Java,成功地编写并部署了一个大型项目,于第二天早上的凌晨五点钟,通过&b&qq&/b&将代码以&b&word文档&/b&形式发给了甲方……&/p&&p&我至今都记得那是宋体。14号。&b&加粗&/b&。&i&方法名还是斜体。&/i&&/p&&p&在此我恳求我的编剧朋友们,今后若是涉及到编程相关领域的剧情编写,下笔之前请务必来问问我……&/p&&p&&b&2)明确学习的方向&/b&&/p&&p&计算机编程领域的分类众多纷杂,不同的划分标准下有不同的开发方向。例如:&/p&&p&按编程语言划分:JAVA,Python,C/C++, Javascript,Ruby,PHP,etc.;&/p&&p&按开发平台划分:Web,IOS,Android,etc.;&/p&&p&按岗位职责划分:前端,后端,运维,测试(黑白盒),数据分析,网络安全,etc.;&/p&&p&按生物种类划分:&b&独一无二的&/b& &a class=&member_mention& href=&//www.zhihu.com/people/39e1a1e41bd11a39bbfd& data-hash=&39e1a1e41bd11a39bbfd& data-hovercard=&p$b$39e1a1e41bd11a39bbfd&&@玲珑邪僧&/a&,其他程序员。&/p&&p&对于之前没有任何编程相关经验的初学者,我的建议是不妨先学习前端相关语言(JS、HTML、CSS)。因为所谓前端开发,即致力于开发客户端上同用户最直接交互的界面,运行即见所得,初期基础项目环境部署相对简单,编译结果可视性强,相对于后端更容易获得最初阶段的满足感,并能获得可持续性发展的兴趣与坚持下去的动力。&/p&&p&例如闲来无事可以做一个简单的h5页面赠与好友: &/p&&figure&&img src=&https://pic2.zhimg.com/v2-f0e4ab417d74cceb8f2ae3dd_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&823& data-rawheight=&390& data-tags=&flat_color& class=&origin_image zh-lightbox-thumb& width=&823& data-original=&https://pic2.zhimg.com/v2-f0e4ab417d74cceb8f2ae3dd_r.jpg&&&/figure&&p&点击切换效果: &/p&&figure&&img src=&https://pic4.zhimg.com/v2-344b758d4dfdbef88a15df_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1068& data-rawheight=&415& class=&origin_image zh-lightbox-thumb& width=&1068& data-original=&https://pic4.zhimg.com/v2-344b758d4dfdbef88a15df_r.jpg&&&/figure&&p&如若不出意外,你便可以顺利收获王丹的高度称赞和一份破裂的昔日友情。&/p&&p&随着前后端的逐步分离,MVC模式里的View和Controller逐渐都归纳于前端领域范畴,如何搭建一个易于维护、性能高效的前端代码框架是一个十分需要探索与钻研的事情,各类前端框架例如Angular、React、Vue也因此应运而生(但切记不可只依赖于框架而不打好原生js的基础)。另外务必打好结实的css基础,因为当你的产品经理进行哪怕一点点的需求变动,你就会深刻体会到什么叫牵一发而动全身。&/p&&p&当然,你若想从事后端开发也未尝不可,只是环境部署相对较为复杂,所需的知识基础也比较晦涩生僻,较易挫伤初学者的信心。毕竟每个想当程序员的初学者心里都住有一个上帝,希望的是随手一指便有光,而不是大手一挥一片黑,再动两下还塌方……&/p&&h2&3)选择合适的学习方式与方法&/h2&&ul&&li&若你是相关专业的在校学生,请务必:&/li&&/ul&&p&&b&1.学好所有相关的专业课程,无论它多么枯燥乏味。&/b&&/p&&p&你若不学计算机组成原理,你便不知道一个int型数据类型占4个字节是何物理意义,更不会透彻理解变量声明和变量定义的区别;你若不学数据结构,你便不知究竟该如何用最优的算法进行最优的深度优化。基础是重中之重,打好基础在工作后能让你甩开大部分同行一条街。&/p&&p&&b&2.不要质疑老师让你阅读代码、抄写代码的意义。&/b&&/p&&p&我曾经对这样的学习方式一度十分嗤之以鼻,自负地认为简直不可理喻,但事实证明,你老师终归是你老师,这种做法一定有其存在的合理性。你能看懂的和你能写下的绝非是同种东西,一旦落笔便能知道,你很可能连静态局部变量和动态局部变量都没有彻底搞清。&/p&&p&例如:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&int fun(int n){
static int f=1;
void main(){
for(i=1;i&=10;i++)
printf(&fun(%d)=%d\n&,i,fun(i));
&/code&&/pre&&/div&&p&static会直接导致输出结果是阶乘还是单纯的乘法算数。而如果你不自己敲一遍,你很可能会忽略这个关键词究竟存在与否。&/p&&p&总而言之,代码在任何时候都不要复制粘贴,请务必自己一点一点进行手写。&/p&&ul&&li&若你是某一特定领域内的初学者:&/li&&/ul&&p&&b&合理充分地利用网络资源,多维度多渠道结合学习,切忌初始便买上几本厚厚的砖头书,进行毫无针对性的泛读而不进行任何实操练习。&/b&&/p&&p&我个人比较喜欢查阅英文的教材和视频资料,表述更为简洁,在编程领域里其实也更为语义化。有关MOOC在线的争议向来较大,不过我个人而言,我是会首选英文MOOC平台进行较为系统学习。面对一个新的领域,明晰的总体脉络走向和精准的切入点,能比单纯的自我学习大幅提高效率。&/p&&p&各类国际化引进MOOC平台中相对成熟著名的有:Udacity,edX, coursera等。不过单就计算机领域的MOOC教育来说,个人比较偏向选择Udacity。由于我最近准备着手转向机器学习正在学习python,故暂且以python入门的预览部分&a href=&//link.zhihu.com/?target=https%3A//cn.udacity.com/course/intro-to-python-nanodegree-foundation--nd000-cn-python& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&cn.udacity.com/course/i&/span&&span class=&invisible&&ntro-to-python-nanodegree-foundation--nd000-cn-python&/span&&span class=&ellipsis&&&/span&&/a&(但似乎先需注册),可看出它的汉化工作较为完善。在初期,若是本身就已经看不懂代码了还特么听不懂人话,这足以让你写出一本python从入门到放弃…… 第二点也是最重要的一点——存在&b&code review&/b&这个环节——在此我要敲黑板强调:&/p&&p&&b&请各位务必珍惜有人在你初期阶段便愿意帮你进行一对一的code review。&/b&&/p&&p&在初步学习阶段,其实有两件事情至关重要:&/p&&p&&b&1.独立开发一个完整的项目;&/b&&/p&&p&&b&2.有专业人员给予代码的审核指导(code review)。&/b&&/p&&p&我之前就职的公司,每天早上10点进行standup是雷打不动的规矩,我被我的reviewer当众吊打也成为特色表演节目之一……虽然被揪头发的感觉痛苦,但是各位务必要记住,一定要珍惜别人给你的review意见,也要多review别人的代码&/p&&p&——毕竟自己的代码自己怎么看都牛逼,只有互相揭穿对方的傻逼才能共同进步……&/p&&p&零基础初学者可以预览相关入门&a href=&//link.zhihu.com/?target=https%3A//cn.udacity.com/course/intro-to-programming-nanodegree--nd000-cn-basic& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&cn.udacity.com/course/i&/span&&span class=&invisible&&ntro-to-programming-nanodegree--nd000-cn-basic&/span&&span class=&ellipsis&&&/span&&/a&(似乎也需先注册),可以体会我所说的以前端为首要切入点、python作为后期配合的架构,与之前在第二章推荐的大致相同,较为合理。&/p&&p&另外,code review也是github的重要存在意义之一。团队项目里提交一个pull request一般来讲是绝不允许自己合并到develop或master主支上的,需要有一个或一个以上的reviewer进行审批后才能确认merge。所以再重申一遍:一定要学会使用github。&/p&&p&此外较为知名的文本教程网站有:&/p&&p&&a href=&//link.zhihu.com/?target=http%3A//www.w3school.com.cn/index.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&w3school 在线教程&/a&,&a href=&//link.zhihu.com/?target=http%3A//www.runoob.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&菜鸟教程&/a&;&/p&&p&w3school有思维导图(这点我认为很好),课程内容也较菜鸟更多,但是我之前更偏向用菜鸟,因为它的用户界面让我觉得比较简洁轻松,w3school相对显得有些庄严……(w3school:?&/p&&p&较为知名的刷题网站有:&/p&&p&lintcode:&a href=&//link.zhihu.com/?target=http%3A//www.lintcode.com/zh-cn/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&lintcode.com/zh-cn/&/span&&span class=&invisible&&&/span&&/a&,&/p&&p&codepad:&a href=&//link.zhihu.com/?target=http%3A//codepad.org/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&codepad.org/&/span&&span class=&invisible&&&/span&&/a&&/p&&p&当有一定的学习经验后,善用在线练习网站进行巩固便变得十分重要。所谓编程,重点在编。相信我,你所能犯的奇葩错误种类绝对超出你对自己的认知……如果不进行实操练习,你永远都不会知道哪个知识点你没有掌握周全。&/p&&p&较为知名的技术博客有:&/p&&p&vczh轮子哥:&a href=&//link.zhihu.com/?target=http%3A//www.cppblog.com/vczh& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&λ-calculus&/a&(C++方向)&/p&&p&赵劼:&a href=&//link.zhihu.com/?target=http%3A//blog.zhaojie.me/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&首页 - 老赵点滴 - 追求编程之美&/a&(C++方向)&/p&&p&&a class=&member_mention& href=&//www.zhihu.com/people/eaf435b228ce0b038a4afe& data-hash=&eaf435b228ce0b038a4afe& data-hovercard=&p$b$eaf435b228ce0b038a4afe&&@路人甲&/a& &a href=&https://zhuanlan.zhihu.com/passer& class=&internal&&学习编程&/a&(Python、数据挖掘方向)&/p&&p&廖雪峰老师:&a href=&//link.zhihu.com/?target=https%3A//www.liaoxuefeng.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&https://www.liaoxuefeng.com&/a&(前端、python方向)&/p&&p&当你遇到一个问题时切莫灰心慌张,你的坑肯定有前人帮你踩过,善于利用技术大牛的博客,会使你事半功倍。&/p&&h2&4)总结&/h2&&p&勤奋钻研,方得始终;&/p&&p&抛砖引玉,与君共勉;&/p&&p&玲珑邪僧,宇宙最强;&/p&&p&综上所述,写得很累……&/p&&p&&br&&/p&&p&————————&br&&br&大家好~&br&&br&我新开通了公众号:万物情史&br&&br&欢迎并诚挚感谢大家关注&/p&
谢几百个邀。这题确实很大,很难讲得面面俱到,我便暂且从我个人经验和角度出发,希望能够起到抛砖引玉的效果,给诸位一些小小的建议。本文共计:4401字,预计阅读时间:7分钟目录(可按需进行定位阅读)前言:明确编程的本质必要的辅助工具与技能明确学习…
&figure&&img src=&https://pic2.zhimg.com/v2-5be1a57d79e7f5a70306_b.jpg& data-rawwidth=&912& data-rawheight=&572& class=&origin_image zh-lightbox-thumb& width=&912& data-original=&https://pic2.zhimg.com/v2-5be1a57d79e7f5a70306_r.jpg&&&/figure&&p&作者:&a href=&http://link.zhihu.com/?target=https%3A//juejin.im/user/57dd4ff12e958a& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lision&/a&
掘金主页:&a href=&http://link.zhihu.com/?target=https%3A//juejin.im/user/57dd4ff12e958a& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Lision 的个人主页&/a&
&/p&&h2&前言&/h2&&p&iOS 开发中总会用到各种缓存,但是各位有没有考虑过什么样的缓存才能被叫做优秀的缓存,或者说优秀的缓存应该具备哪些特质?&/p&&p&&b&闭上眼睛,想一想如果面试官让你设计一个缓存你会怎么回答?&/b&&/p&&p&本文将结合 YYCache 的源码逐步带大家找到答案。&/p&&p&&a href=&http://link.zhihu.com/?target=https%3A//github.com/ibireme/YYCache& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYCache&/a& 是一个线程安全的高性能键值缓存(该项目是 &a href=&http://link.zhihu.com/?target=https%3A//github.com/ibireme/YYKit& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYKit&/a& 组件之一)。YYKit 是在 2015 年发布到 Github 的,由于其代码质量很高,在短时间内就收获了大量的 Star(目前已经 1w+ Star 了),而且在 iOS 各大社区反响广泛,Google 一下也是漫天赞叹。&/p&&p&YYKit 作者是 &a href=&http://link.zhihu.com/?target=https%3A//github.com/ibireme& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@ibireme&/a&,原名郭曜源(猜测 YY 前缀来源于曜源?),是我个人非常喜欢的国人开发者(何止喜欢,简直是迷弟 )。&/p&&p&YYCache 的代码逻辑清晰,注释详尽,加上自身不算太大的代码量使得其阅读非常简单,更加难能可贵的是它的性能还非常高。&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-2de3e7eef8aafd3bab4d4_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1456& data-rawheight=&1000& class=&origin_image zh-lightbox-thumb& width=&1456& data-original=&https://pic1.zhimg.com/v2-2de3e7eef8aafd3bab4d4_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-6a64dccb59c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1456& data-rawheight=&794& class=&origin_image zh-lightbox-thumb& width=&1456& data-original=&https://pic1.zhimg.com/v2-6a64dccb59c_r.jpg&&&/figure&&p&&br&&/p&&p&我对它的评价是&b&小而美&/b&,这种小而美的缓存源码对于我们今天的主题太合适不过了(本文中 YYCache 源码版本为 v1.0.4)。&/p&&h2&索引&/h2&&ul&&li&YYCache 简介&/li&&li&YYMemoryCache 细节剖析&/li&&li&YYDiskCache 细节剖析&/li&&li&优秀的缓存应该具备哪些特质&/li&&li&总结&/li&&/ul&&h2&YYCache 简介&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-c56118cdeb54f65f2a783e0_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1418& data-rawheight=&628& class=&origin_image zh-lightbox-thumb& width=&1418& data-original=&https://pic1.zhimg.com/v2-c56118cdeb54f65f2a783e0_r.jpg&&&/figure&&p&&br&&/p&&p&简单把 YYCache 从头到尾撸了一遍,最大的感触就是代码风格干净整洁,代码思路清晰明了。&/p&&p&由于代码整体阅读难度不是非常大,本文不会去逐字逐句的解读源码,而是提炼 YYCache 作为一个小而美的缓存实现了哪些缓存该具备的特质,并且分析实现细节。&/p&&p&我们先来简单看一下 YYCache 的代码结构,YYCache 是由 YYMemoryCache 与 YYDiskCache 两部分组成的,其中 YYMemoryCache 作为高速内存缓存,而 YYDiskCache 则作为低速磁盘缓存。&/p&&blockquote&通常一个缓存是由内存缓存和磁盘缓存组成,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。&/blockquote&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@interface YYCache : NSObject
@property (copy, readonly) NSString *
@property (strong, readonly) YYMemoryCache *memoryC
@property (strong, readonly) YYDiskCache *diskC
- (BOOL)containsObjectForKey:(NSString *)
- (nullable id&NSCoding&)objectForKey:(NSString *)
- (void)setObject:(nullable id&NSCoding&)object forKey:(NSString *)
- (void)removeObjectForKey:(NSString *)
&/code&&/pre&&/div&&p&上面的代码我做了简化,只保留了最基本的代码(我认为作者在最初设计 YYCache 雏形时很可能也只是提供了这些基本的接口),其他的接口只是通过调用基本的接口再附加对应处理代码而成。&/p&&blockquote&Note: 其实源码中作者用了一些技巧性的宏,例如 &code&NS_ASSUME_NONNULL_BEGIN&/code&与 &code&NS_ASSUME_NONNULL_END&/code& 来通过编译器层检测入参是否为空并给予警告,参见 &a href=&http://link.zhihu.com/?target=https%3A//developer.apple.com/swift/blog/%3Fid%3D25& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Nullability and Objective-C&/a&。&br&类似上述的编码技巧还有很多,我并非不想与大家分享我 get 到的这些编码技巧,只是觉得它与本文的主题似乎不太相符。我准备在之后专门写一篇文章来与大家分享我在阅读各大源码库过程中 get 到的编码技巧(感兴趣的话可以 &a href=&http://link.zhihu.com/?target=https%3A//weibo.com//profile& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&关注我&/a&)。&/blockquote&&p&从代码中我们可以看到 YYCache 中持有 YYMemoryCache 与 YYDiskCache,并且对外提供了一些接口。这些接口基本都是基于 Key 和 Value 设计的,类似于 iOS 原生的字典类接口(增删改查)。&/p&&h2&YYMemoryCache 细节剖析&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-567bab74ea4fbffe577c65ffe3a7bded_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1200& data-rawheight=&671& class=&origin_image zh-lightbox-thumb& width=&1200& data-original=&https://pic2.zhimg.com/v2-567bab74ea4fbffe577c65ffe3a7bded_r.jpg&&&/figure&&p&&br&&/p&&p&YYMemoryCache 是一个高速的内存缓存,用于存储键值对。它与 NSDictionary 相反,Key 被保留并且不复制。API 和性能类似于 NSCache,所有方法都是线程安全的。&/p&&p&YYMemoryCache 对象与 NSCache 的不同之处在于:&/p&&ul&&li&YYMemoryCache 使用 LRU(least-recently-used) 算法来驱逐对象;NSCache 的驱逐方式是非确定性的。&/li&&li&YYMemoryCache 提供 age、cost、count 三种方式控制缓存;NSCache 的控制方式是不精确的。&/li&&li&YYMemoryCache 可以配置为在收到内存警告或者 App 进入后台时自动逐出对象。&/li&&/ul&&blockquote&Note: YYMemoryCache 中的 &code&Access Methods&/code& 消耗时长通常是稳定的&code&(O(1))&/code&。&/blockquote&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@interface YYMemoryCache : NSObject
#pragma mark - Attribute
@property (nullable, copy) NSString * // 缓存名称,默认为 nil
@property (readonly) NSUInteger totalC // 缓存对象总数
@property (readonly) NSUInteger totalC // 缓存对象总开销
#pragma mark - Limit
@property NSUInteger countL // 缓存对象数量限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSUInteger costL // 缓存开销数量限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSTimeInterval ageL // 缓存时间限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSTimeInterval autoTrimI // 缓存自动清理时间间隔,默认 5s
@property BOOL shouldRemoveAllObjectsOnMemoryW // 是否应该在收到内存警告时删除所有缓存内对象
@property BOOL shouldRemoveAllObjectsWhenEnteringB // 是否应该在 App 进入后台时删除所有缓存内对象
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache); // 我认为这是一个 hook,便于我们在收到内存警告时自定义处理缓存
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache); // 我认为这是一个 hook,便于我们在收到 App 进入后台时自定义处理缓存
@property BOOL releaseOnMainT // 是否在主线程释放对象,默认 NO,有些对象(例如 UIView/CALayer)应该在主线程释放
@property BOOL releaseA // 是否异步释放对象,默认 YES
- (BOOL)containsObjectForKey:(id)
- (nullable id)objectForKey:(id)
- (void)setObject:(nullable id)object forKey:(id)
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)
- (void)removeObjectForKey:(id)
- (void)removeAllO
#pragma mark - Trim
- (void)trimToCount:(NSUInteger) // 用 LRU 算法删除对象,直到 totalCount &= count
- (void)trimToCost:(NSUInteger) // 用 LRU 算法删除对象,直到 totalCost &= cost
- (void)trimToAge:(NSTimeInterval) // 用 LRU 算法删除对象,直到所有到期对象全部被删除
&/code&&/pre&&/div&&p&YYMemoryCache 的定义代码比较简单~ 该有的注释我已经加到了上面,这里 LRU 算法的实现我准备单独拎出来放到后面和(&code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code&)一起讲。我们这里只需要再关注一下 YYMemoryCache 是如何做到线程安全的。&/p&&h2&YYMemoryCache 是如何做到线程安全的&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@implementation YYMemoryCache {
pthread_mutex_t _ // 线程锁,旨在保证 YYMemoryCache 线程安全
_YYLinkedMap *_ // _YYLinkedMap,YYMemoryCache 通过它间接操作缓存对象
dispatch_queue_t _ // 串行队列,用于 YYMemoryCache 的 trim 操作
&/code&&/pre&&/div&&p&没错,这里 ibireme 选择使用 &code&pthread_mutex&/code& 线程锁来确保 YYMemoryCache 的线程安全。&/p&&blockquote&有趣的是,这里 ibireme 使用 &code&pthread_mutex&/code& 是有一段小故事的。在最初 YYMemoryCache 这里使用的锁是 &code&OSSpinLock&/code& 自旋锁(详见 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//yycache/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYCache 设计思路&/a& 备注-关于锁),后面有人在 Github 向作者提 &a href=&http://link.zhihu.com/?target=https%3A//github.com/ibireme/YYModel/issues/43& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&issue&/a& 反馈 &code&OSSpinLock&/code&不安全,经过作者的确认(详见 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//spinlock_is_unsafe_in_ios/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&不再安全的 OSSpinLock&/a&)最后选择用&code&pthread_mutex&/code& 替代 &code&OSSpinLock&/code&。&/blockquote&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-53de616ccd200a5ed918a827baade577_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1060& data-rawheight=&684& class=&origin_image zh-lightbox-thumb& width=&1060& data-original=&https://pic4.zhimg.com/v2-53de616ccd200a5ed918a827baade577_r.jpg&&&/figure&&p&&br&&/p&&p&上面是 ibireme 在确认 &code&OSSpinLock&/code& 不再安全之后为了寻找替代方案做的简单性能测试,对比了一下几种能够替代 &code&OSSpinLock&/code& 锁的性能。在 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//spinlock_is_unsafe_in_ios/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&不再安全的 OSSpinLock&/a& 文末的评论中,我找到了作者使用 &code&pthread_mutex&/code& 的原因。&/p&&blockquote&ibireme: 苹果员工说 libobjc 里 &code&spinlock&/code& 是用了一些私有方法 (&code&mach_thread_switch&/code&),贡献出了高线程的优先来避免优先级反转的问题,但是我翻了下 libdispatch 的源码倒是没发现相关逻辑,也可能是我忽略了什么。在我的一些测试中,&code&OSSpinLock&/code& 和 &code&dispatch_semaphore&/code& 都不会产生特别明显的死锁,所以我也无法确定用 &code&dispatch_semaphore&/code& 代替 &code&OSSpinLock&/code& 是否正确。能够肯定的是,用 &code&pthread_mutex&/code& 是安全的。&/blockquote&&h2&&code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code&&/h2&&p&上文介绍了 YYMemoryCache,其实 YYMemoryCache 并不直接操作缓存对象,而是通过内部的 &code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code& 来间接的操作缓存对象。这两个类对于上文中提到的 LRU 缓存算法的理解至关重要,所以我把他们俩单独拎出来放在这里详细解读一下。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/**
_YYLinkedMap 中的一个节点。
通常情况下我们不应该使用这个类。
@interface _YYLinkedMapNode : NSObject {
__unsafe_unretained _YYLinkedMapNode *_ // __unsafe_unretained 是为了性能优化,节点被 _YYLinkedMap 的 _dic 强引用
__unsafe_unretained _YYLinkedMapNode *_ // __unsafe_unretained 是为了性能优化,节点被 _YYLinkedMap 的 _dic 强引用
NSUInteger _ // 记录开销,对应 YYMemoryCache 提供的 cost 控制
NSTimeInterval _ // 记录时间,对应 YYMemoryCache 提供的 age 控制
YYMemoryCache 内的一个链表。
_YYLinkedMap 不是一个线程安全的类,而且它也不对参数做校验。
通常情况下我们不应该使用这个类。
@interface _YYLinkedMap : NSObject {
CFMutableDictionaryRef _ // 不要直接设置该对象
NSUInteger _totalC
NSUInteger _totalC
_YYLinkedMapNode *_ // MRU, 最常用节点,不要直接修改它
_YYLinkedMapNode *_ // LRU, 最少用节点,不要直接修改它
BOOL _releaseOnMainT // 对应 YYMemoryCache 的 releaseOnMainThread
BOOL _releaseA // 对应 YYMemoryCache 的 releaseAsynchronously
// 链表操作,看接口名称应该不需要注释吧~
- (void)insertNodeAtHead:(_YYLinkedMapNode *)
- (void)bringNodeToHead:(_YYLinkedMapNode *)
- (void)removeNode:(_YYLinkedMapNode *)
- (_YYLinkedMapNode *)removeTailN
- (void)removeA
&/code&&/pre&&/div&&p&为了方便大家阅读,我标注了必要的中文注释。其实对数据结构与算法不陌生的同学应该一眼就看的出来 &code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code& 这俩货的本质。没错,丫就是双向链表节点和双向链表。&/p&&p&&code&_YYLinkedMapNode&/code& 作为双向链表节点,除了基本的 &code&_prev&/code&、&code&_next&/code&,还有键值缓存基本的 &code&_key&/code& 与 &code&_value&/code&,&b&我们可以把&/b& &code&&b&_YYLinkedMapNode&/b&&/code& &b&理解为 YYMemoryCache 中的一个缓存对象&/b&。&/p&&p&&code&_YYLinkedMap&/code& 作为由 &code&_YYLinkedMapNode&/code& 节点组成的双向链表,使用&code&CFMutableDictionaryRef _dic&/code& 字典存储 &code&_YYLinkedMapNode&/code&。这样在确保&code&_YYLinkedMapNode&/code& 被强引用的同时,能够利用字典的 Hash 快速定位用户要访问的缓存对象,这样既符合了键值缓存的概念又省去了自己实现的麻烦(笑)。&/p&&p&嘛~ 总得来说 YYMemoryCache 是通过使用 &code&_YYLinkedMap&/code& 双向链表来操作&code&_YYLinkedMapNode&/code& 缓存对象节点的。&/p&&h2&LRU(least-recently-used) 算法的实现&/h2&&p&上文我们认清了 &code&_YYLinkedMap&/code& 与 &code&_YYLinkedMapNode&/code& 本质上就是双向链表和链表节点,这里我们简单讲一下 YYMemoryCache 是如何利用双向链表实现 LRU(least-recently-used) 算法的。&/p&&h2&缓存替换策略&/h2&&p&首先 LRU 是缓存替换策略(&a href=&http://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Cache_replacement_policies& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Cache replacement policies&/a&)的一种,还有很多缓存替换策略诸如:&/p&&ul&&li&First In First Out (FIFO)&/li&&li&Last In First Out (LIFO)&/li&&li&Time aware Least Recently Used (TLRU)&/li&&li&Most Recently Used (MRU)&/li&&li&Pseudo-LRU (PLRU)&/li&&li&Random Replacement (RR)&/li&&li&Segmented LRU (SLRU)&/li&&li&Least-Frequently Used (LFU)&/li&&li&Least Frequent Recently Used (LFRU)&/li&&li&LFU with Dynamic Aging (LFUDA)&/li&&li&Low Inter-reference Recency Set (LIRS)&/li&&li&Adaptive Replacement Cache (ARC)&/li&&li&Clock with Adaptive Replacement (CAR)&/li&&li&Multi Queue (MQ) caching algorithm|Multi Queue (MQ)&/li&&li&Pannier: Container-based caching algorithm for compound objects&/li&&/ul&&p&是不是被唬到了?不要担心,我这里会表述的尽量易懂。&/p&&h2&缓存命中率&/h2&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8aa4985cff85daafdce68da1_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1000& data-rawheight=&320& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&https://pic2.zhimg.com/v2-8aa4985cff85daafdce68da1_r.jpg&&&/figure&&p&&br&&/p&&p&为什么有这么多缓存替换策略,或者说搞这么多名堂究竟是为了什么呢?&/p&&p&答案是提高缓存命中率,那么何谓缓存命中率呢?&/p&&p&Google 一下自然是有不少解释,不过很多都是 web 相关的,而且不说人话(很难理解),我个人非常讨厌各种不说人话的“高深”抽象概念。&/p&&p&这里抖了好几抖胆才敢谈一下我对于缓存命中率的理解(限于 YYCache 和 iOS 开发)。&/p&&ul&&li&缓存命中 = 用户要访问的缓存对象在高速缓存中,我们直接在高速缓存中通过 Hash 将其找到并返回给用户。&/li&&li&缓存命中率 = 用户要访问的缓存对象在高速缓存中被我们访问到的概率。&/li&&/ul&&p&既然谈到了自己的理解,我索性说个够。&/p&&ul&&li&缓存丢失 = 由于高速缓存数量有限(占据内存等原因),所以用户要访问的缓存对象很有可能被我们从有限的高速缓存中淘汰掉了,我们可能会将其存储于低速的磁盘缓存中(如果磁盘缓存还有资源的话),那么就要从磁盘缓存中获取该缓存对象以返回给用户,这种情况我理解为(高速)缓存未命中,即缓存丢失(并不是真的被我们丢掉了,但肯定是被我们从高速缓存淘汰掉了)。&/li&&/ul&&p&缓存命中是 cache-hit,那么如果你玩游戏,可以理解为这次 hit miss 了(笑,有人找我开黑吗)。&/p&&h2&LRU&/h2&&p&首先来讲一下 LRU 的概念让大家有一个基本的认识。LRU(least-recently-used) 翻译过来是“最近使用”,顾名思义这种缓存替换策略是基于用户最近访问过的缓存对象而建立。&/p&&p&我认为 LRU 缓存替换策略的核心思想在于:LRU 认为用户最新使用(访问)过的缓存对象为高频缓存对象,即用户很可能还会再次使用(访问)该缓存对象;而反之,用户很久之前使用(访问)过的缓存对象(期间一直没有再次访问)为低频缓存对象,即用户很可能不会再去使用(访问)该缓存对象,通常在资源不足时会先去释放低频缓存对象。&/p&&h2&&code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code& 实现 LRU&/h2&&p&YYCache 作者通过 &code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code& 双向链表实现 LRU 缓存替换策略的思路其实很简捷清晰,我们一步一步来看。&/p&&p&双向链表中有头结点和尾节点:&/p&&ul&&li&头结点 = 链表中用户最近一次使用(访问)的缓存对象节点,MRU。&/li&&li&尾节点 = 链表中用户已经很久没有再次使用(访问)的缓存对象节点,LRU。&/li&&/ul&&p&如何让头结点和尾节点指向我们想指向的缓存对象节点?我们结合代码来看:&/p&&ul&&li&在用户使用(访问)时更新缓存节点信息,并将其移动至双向链表头结点。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&- (id)objectForKey:(id)key {
// 判断入参
pthread_mutex_lock(&_lock);
// 找到对应缓存节点
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru-&_dic, (__bridge const void *)(key));
if (node) {
// 更新缓存节点时间,并将其移动至双向链表头结点
node-&_time = CACurrentMediaTime();
[_lru bringNodeToHead:node];
pthread_mutex_unlock(&_lock);
// 返回找到的缓存节点 value
return node ? node-&_value :
&/code&&/pre&&/div&&ul&&li&在用户设置缓存对象时,判断入参 key 对应的缓存对象节点是否存在?存在则更新缓存对象节点并将节点移动至链表头结点;不存在则根据入参生成新的缓存对象节点并插入链表表头。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
// 判断入参,省略
pthread_mutex_lock(&_lock);
// 判断入参 key 对应的缓存对象节点是否存在
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru-&_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
// 存在则更新缓存对象节点并将节点移动至链表头结点
_lru-&_totalCost -= node-&_
_lru-&_totalCost +=
node-&_cost =
node-&_time =
node-&_value =
[_lru bringNodeToHead:node];
// 不存在则根据入参生成新的缓存对象节点并插入链表表头
node = [_YYLinkedMapNode new];
node-&_cost =
node-&_time =
node-&_key =
node-&_value =
[_lru insertNodeAtHead:node];
// 判断插入、更新节点之后是否超过了限制 cost、count,如果超过则 trim,省略
pthread_mutex_unlock(&_lock);
&/code&&/pre&&/div&&ul&&li&在资源不足时,从双线链表的尾节点(LRU)开始清理缓存,释放资源。&/li&&/ul&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 这里拿 count 资源举例,cost、age 自己举一反三
- (void)_trimToCount:(NSUInteger)countLimit {
// 判断 countLimit 为 0,则全部清空缓存,省略
// 判断 _lru-&_totalCount &= countLimit,没有超出资源限制则不作处理,省略
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru-&_totalCount & countLimit) {
// 从双线链表的尾节点(LRU)开始清理缓存,释放资源
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
finish = YES;
pthread_mutex_unlock(&_lock);
// 使用 usleep 以微秒为单位挂起线程,在短时间间隔挂起线程
// 对比 sleep 用 usleep 能更好的利用 CPU 时间
usleep(10 * 1000); //10 ms
// 判断是否需要在主线程释放,采取释放缓存对象操作
if (holder.count) {
dispatch_queue_t queue = _lru-&_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
// 异步释放,我们单独拎出来讲
[holder count]; // release in queue
&/code&&/pre&&/div&&p&嘛~ 是不是感觉敲简单?上面代码去掉了可能会分散大家注意力的代码,我们这里仅仅讨论 LRU 的实现,其余部分的具体实现源码也非常简单,我觉得没必要贴出来单独讲解,感兴趣的同学可以自己去 &a href=&http://link.zhihu.com/?target=https%3A//github.com/ibireme/YYCache& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYCache&/a& 下载源码查阅。&/p&&h2&异步释放技巧&/h2&&p&关于上面的异步释放缓存对象的代码,我觉得还是有必要单独拎出来讲一下的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&dispatch_queue_t queue = _lru-&_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
// 异步释放,我们单独拎出来讲
[holder count]; // release in queue
&/code&&/pre&&/div&&p&这个技巧 ibireme 在他的另一篇文章 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//smooth_user_interfaces_for_ios/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&iOS 保持界面流畅的技巧&/a& 中有提及:&/p&&blockquote&Note: 对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。同样的,如果对象可以放到后台线程去释放,那就挪到后台线程去。这里有个小 Tip:把对象捕获到 block 中,然后扔到后台队列去随便发送个消息以避免编译器警告,就可以让对象在后台线程销毁了。&/blockquote&&p&而上面代码中的 YYMemoryCacheGetReleaseQueue 这个队列源码为:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 静态内联 dispatch_queue_t
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
&/code&&/pre&&/div&&p&在源码中可以看到 YYMemoryCacheGetReleaseQueue 是一个低优先级&code&DISPATCH_QUEUE_PRIORITY_LOW&/code& 队列,猜测这样设计的原因是可以让 iOS 在系统相对空闲时再来异步释放缓存对象。&/p&&h2&YYDiskCache 细节剖析&/h2&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-080a74b8d5ce3c27c5e94fc14c584f3f_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1890& data-rawheight=&981& class=&origin_image zh-lightbox-thumb& width=&1890& data-original=&https://pic4.zhimg.com/v2-080a74b8d5ce3c27c5e94fc14c584f3f_r.jpg&&&/figure&&p&&br&&/p&&p&YYDiskCache 是一个线程安全的磁盘缓存,用于存储由 SQLite 和文件系统支持的键值对(类似于 NSURLCache 的磁盘缓存)。&/p&&p&YYDiskCache 具有以下功能:&/p&&ul&&li&它使用 LRU(least-recently-used) 来删除对象。&/li&&li&支持按 cost,count 和 age 进行控制。&/li&&li&它可以被配置为当没有可用的磁盘空间时自动驱逐缓存对象。&/li&&li&它可以自动抉择每个缓存对象的存储类型(sqlite/file)以便提供更好的性能表现。&/li&&/ul&&blockquote&Note: 您可以编译最新版本的 sqlite 并忽略 iOS 系统中的 libsqlite3.dylib 来获得 2x?4x 的速度提升。&/blockquote&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@interface YYDiskCache : NSObject
#pragma mark - Attribute
@property (nullable, copy) NSString * // 缓存名称,默认为 nil
@property (readonly) NSString * // 缓存路径
@property (readonly) NSUInteger inlineT // 阈值,大于阈值则存储类型为 file;否则存储类型为 sqlite
@property (nullable, copy) NSData *(^customArchiveBlock)(id object); // 用来替换 NSKeyedArchiver,你可以使用该代码块以支持没有 conform `NSCoding` 协议的对象
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data); // 用来替换 NSKeyedUnarchiver,你可以使用该代码块以支持没有 conform `NSCoding` 协议的对象
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key); // 当一个对象将以 file 的形式保存时,该代码块用来生成指定文件名。如果为 nil,则默认使用 md5(key) 作为文件名
#pragma mark - Limit
@property NSUInteger countL // 缓存对象数量限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSUInteger costL // 缓存开销数量限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSTimeInterval ageL // 缓存时间限制,默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSUInteger freeDiskSpaceL // 缓存应该保留的最小可用磁盘空间(以字节为单位),默认无限制,超过限制则会在后台逐出一些对象以满足限制
@property NSTimeInterval autoTrimI // 缓存自动清理时间间隔,默认 60s
@property BOOL errorLogsE // 是否开启错误日志
#pragma mark - Initializer
- (nullable instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;
- (BOOL)containsObjectForKey:(NSString *)
- (nullable id&NSCoding&)objectForKey:(NSString *)
- (void)setObject:(nullable id&NSCoding&)object forKey:(NSString *)
- (void)removeObjectForKey:(NSString *)
- (void)removeAllO
- (NSInteger)totalC
- (NSInteger)totalC
#pragma mark - Trim
- (void)trimToCount:(NSUInteger)
- (void)trimToCost:(NSUInteger)
- (void)trimToAge:(NSTimeInterval)
#pragma mark - Extended Data
+ (nullable NSData *)getExtendedDataFromObject:(id)
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)
&/code&&/pre&&/div&&p&YYDiskCache 结构与 YYMemoryCache 类似,由于很多接口都是基于基本的接口做了扩展所得,这里贴的代码省略了一些接口。代码还是一如既往的干净简洁,相信各位都能看懂。&/p&&p&YYDiskCache 是基于 sqlite 和 file 来做的磁盘缓存,我们的缓存对象可以自由的选择存储类型,下面简单对比一下:&/p&&ul&&li&sqlite: 对于小数据(例如 NSNumber)的存取效率明显高于 file。&/li&&li&file: 对于较大数据(例如高质量图片)的存取效率优于 sqlite。&/li&&/ul&&p&所以 YYDiskCache 使用两者配合,灵活的存储以提高性能。&/p&&h2&NSMapTable&/h2&&p&NSMapTable 是类似于字典的集合,但具有更广泛的可用内存语义。NSMapTable 是 iOS6 之后引入的类,它基于 NSDictionary 建模,但是具有以下差异:&/p&&ul&&li&键/值可以选择 “weakly” 持有,以便于在回收其中一个对象时删除对应条目。&/li&&li&它可以包含任意指针(其内容不被约束为对象)。&/li&&li&您可以将 NSMapTable 实例配置为对任意指针进行操作,而不仅仅是对象。&/li&&/ul&&blockquote&Note: 配置映射表时,请注意,只有 NSMapTableOptions 中列出的选项才能保证其余的 API 能够正常工作,包括复制,归档和快速枚举。 虽然其他 NSPointerFunctions 选项用于某些配置,例如持有任意指针,但并不是所有选项的组合都有效。使用某些组合,NSMapTableOptions 可能无法正常工作,甚至可能无法正确初始化。&/blockquote&&p&更多信息详见 &a href=&http://link.zhihu.com/?target=https%3A//developer.apple.com/documentation/foundation/nsmaptable%3Flanguage%3Dobjc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NSMapTable 官方文档&/a&。&/p&&p&需要特殊说明的是,YYDiskCache 内部是基于一个单例 NSMapTable 管理的,这点有别于 YYMemoryCache。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&static NSMapTable *_globalI // 引用管理所有的 YYDiskCache 实例
static dispatch_semaphore_t _globalInstancesL // YYDiskCache 使用 dispatch_semaphore 保障 NSMapTable 线程安全
static void _YYDiskCacheInitGlobal() {
static dispatch_once_t onceT
dispatch_once(&onceToken, ^{
_globalInstancesLock = dispatch_semaphore_create(1);
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
if (path.length == 0)
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
id cache = [_globalInstances objectForKey:path];
dispatch_semaphore_signal(_globalInstancesLock);
static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
if (cache.path.length == 0)
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
[_globalInstances setObject:cache forKey:cache.path];
dispatch_semaphore_signal(_globalInstancesLock);
&/code&&/pre&&/div&&p&每当一个 YYDiskCache 被初始化时,其实会先到 NSMapTable 中获取对应 path 的 YYDiskCache 实例,如果获取不到才会去真正的初始化一个 YYDiskCache 实例,并且将其引用在 NSMapTable 中,这样做也会提升不少性能。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
// 判断是否可以成功初始化,省略
// 先从 NSMapTable 单例中根据 path 获取 YYDiskCache 实例,如果获取到就直接返回该实例
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
if (globalCache) return globalC
// 没有获取到则初始化一个 YYDiskCache 实例
// 要想初始化一个 YYDiskCache 首先要初始化一个 YYKVStorage
YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
// 根据刚才得到的 kv 和 path 入参初始化一个 YYDiskCache 实例,代码太长省略
// 开启递归清理,会根据 _autoTrimInterval 对 YYDiskCache trim
[self _trimRecursively];
// 向 NSMapTable 单例注册新生成的 YYDiskCache 实例
_YYDiskCacheSetGlobal(self);
// App 生命周期通知相关代码,省略
&/code&&/pre&&/div&&p&我在 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//yycache/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYCache 设计思路&/a& 中找到了作者使用 dispatch_semaphore 作为 YYDiskCache 锁的原因:&/p&&blockquote&dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说,它比较合适。&/blockquote&&h2&YYKVStorageItem 与 YYKVStorage&/h2&&p&刚才在 YYDiskCache 的初始化源码中,我们不难发现一个类 YYKVStorage。与 YYMemoryCache 相对应的,YYDiskCache 也不会直接操作缓存对象(sqlite/file),而是通过 YYKVStorage 来间接的操作缓存对象。&/p&&p&从这一点上不难发现,YYKVStorage 等价于 YYMemoryCache 中的双向链表&code&_YYLinkedMap&/code&,而对应于 &code&_YYLinkedMap&/code& 中的节点 &code&_YYLinkedMapNode&/code&,YYKVStorage 中也有一个类 YYKVStorageItem 充当着与缓存对象一对一的角色。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// YYKVStorageItem 是 YYKVStorage 中用来存储键值对和元数据的类
// 通常情况下,我们不应该直接使用这个类
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *
@property (nonatomic, strong) NSData *
///& value
@property (nullable, nonatomic, strong) NSString * ///& filename (nil if inline)
@property (nonatomic)
///& value's size in bytes
@property (nonatomic) int modT
///& modification unix timestamp
@property (nonatomic) int accessT
///& last access unix timestamp
@property (nullable, nonatomic, strong) NSData *extendedD ///& extended data (nil if no extended data)
YYKVStorage 是基于 sqlite 和文件系统的键值存储。
通常情况下,我们不应该直接使用这个类。
这个类的实例是 *非* 线程安全的,你需要确保
只有一个线程可以同时访问该实例。如果你真的
需要在多线程中处理大量的数据,应该分割数据
到多个 KVStorage 实例(分片)。
@interface YYKVStorage : NSObject
#pragma mark - Attribute
@property (nonatomic, readonly) NSString *
/// storage 路径
@property (nonatomic, readonly) YYKVStorageT
/// storage 类型
@property (nonatomic) BOOL errorLogsE
/// 是否开启错误日志
#pragma mark - Initializer
- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER;
#pragma mark - Save Items
- (BOOL)saveItem:(YYKVStorageItem *)
#pragma mark - Remove Items
- (BOOL)removeItemForKey:(NSString *)
#pragma mark - Get Items
- (nullable YYKVStorageItem *)getItemForKey:(NSString *)
#pragma mark - Get Storage Status
- (BOOL)itemExistsForKey:(NSString *)
- (int)getItemsC
- (int)getItemsS
&/code&&/pre&&/div&&p&代码美哭了有木有!?这种代码根本不需要翻译,我觉得相比于逐行的翻译,直接看代码更舒服。这里我们只需要看一下 YYKVStorageType 这个枚举,他决定着 YYKVStorage 的存储类型。&/p&&h2&YYKVStorageType&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&/**
存储类型,指示“YYKVStorageItem.value”存储在哪里。
@discussion
通常,将数据写入 sqlite 比外部文件更快,但是
读取性能取决于数据大小。在我的测试(环境 iPhone 6 64G),
当数据较大(超过 20KB)时从外部文件读取数据比 sqlite 更快。
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
YYKVStorageTypeFile = 0, // value 以文件的形式存储于文件系统
YYKVStorageTypeSQLite = 1, // value 以二进制形式存储于 sqlite
YYKVStorageTypeMixed = 2, // value 将根据你的选择基于上面两种形式混合存储
&/code&&/pre&&/div&&p&在 YYKVStorageType 的注释中标记了作者写 YYCache 时做出的测试结论,大家也可以基于自己的环境去测试验证作者的说法(这一点是可以讨论的,我们可以根据自己的测试来设置 YYDiskCache 中的 inlineThreshold 阈值)。&/p&&blockquote&如果想要了解更多的信息可以点击 &a href=&http://link.zhihu.com/?target=http%3A//www.sqlite.org/intern-v-extern-blob.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Internal Versus External BLOBs in SQLite&/a& 查阅 SQLite 官方文档。&/blockquote&&h2&YYKVStorage 性能优化细节&/h2&&p&上文说到 YYKVStorage 可以基于 SQLite 和文件系统做磁盘存储,这里再提一些我阅读源码发现到的有趣细节:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&@implementation YYKVStorage {
CFMutableDictionaryRef _dbStmtC // 焦点集中在这里
&/code&&/pre&&/div&&p&可以看到 &code&CFMutableDictionaryRef _dbStmtC&/code& 是 YYKVStorage 中的私有成员,它是一个可变字典充当着 sqlite3_stmt 缓存的角色。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
// 先尝试从 _dbStmtCache 根据入参 sql 取出已缓存 sqlite3_stmt
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
if (!stmt) {
// 如果没有缓存再从新生成一个 sqlite3_stmt
int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
// 生成结果异常则根据错误日志开启标识打印日志
if (result != SQLITE_OK) {
if (_errorLogsEnabled) NSLog(@&%s line:%d sqlite stmt prepare error (%d): %s&, __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NULL;
// 生成成功则放入 _dbStmtCache 缓存
CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
sqlite3_reset(stmt);
&/code&&/pre&&/div&&p&这样就可以省去一些重复生成 sqlite3_stmt 的开销。&/p&&blockquote&sqlite3_stmt: 该对象的实例表示已经编译成二进制形式并准备执行的单个 SQL 语句。&/blockquote&&p&更多关于 SQLite 的信息请点击 &a href=&http://link.zhihu.com/?target=http%3A//www.sqlite.org/docs.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&SQLite 官方文档&/a& 查阅。&/p&&h2&优秀的缓存应该具备哪些特质&/h2&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ac260e0c2301aef48aca1c_b.jpg& data-caption=&& data-size=&normal& data-rawwidth=&1800& data-rawheight=&1100& class=&origin_image zh-lightbox-thumb& width=&1800& data-original=&https://pic1.zhimg.com/v2-ac260e0c2301aef48aca1c_r.jpg&&&/figure&&p&&br&&/p&&p&嘛~ 我们回到文章最初提到的问题,优秀的缓存应该具备哪些特质?&/p&&p&如果跟着文章一步步读到这里,相信很容易举出以下几点:&/p&&ul&&li&内存缓存和磁盘缓存&/li&&li&线程安全&/li&&li&缓存控制&/li&&li&缓存替换策略&/li&&li&缓存命中率&/li&&li&性能&/li&&/ul&&p&我们简单的总结一下 YYCache 源码中是如何体现这些特质的。&/p&&h2&内存缓存和磁盘缓存&/h2&&p&YYCache 是由内存缓存 YYMemoryCache 与磁盘缓存 YYDiskCache 相互配合组成的,内存缓存提供容量小但高速的存取功能,磁盘缓存提供大容量但低速的持久化存储。这样的设计支持用户在缓存不同对象时都能够有很好的体验。&/p&&p&在 YYCache 中使用接口访问缓存对象时,会先去尝试从内存缓存 YYMemoryCache 中访问,如果访问不到(没有使用该 key 缓存过对象或者该对象已经从容量有限的 YYMemoryCache 中淘汰掉)才会去从 YYDiskCache 访问,如果访问到(表示之前确实使用该 key 缓存过对象,该对象已经从容量有限的 YYMemoryCache 中淘汰掉成立)会先在 YYMemoryCache 中更新一次该缓存对象的访问信息之后才返回给接口。&/p&&h2&线程安全&/h2&&p&如果说 YYCache 这个类是一个纯逻辑层的缓存类(指 YYCache 的接口实现全部是调用其他类完成),那么 YYMemoryCache 与 YYDiskCache 还是做了一些事情的(并没有 YYCache 当甩手掌柜那么轻松),其中最显而易见的就是 YYMemoryCache 与 YYDiskCache 为 YYCache 保证了线程安全。&/p&&p&YYMemoryCache 使用了 &code&pthread_mutex&/code& 线程锁来确保线程安全,而 YYDiskCache 则选择了更适合它的 &code&dispatch_semaphore&/code&,上文已经给出了作者选择这些锁的原因。&/p&&h2&缓存控制&/h2&&p&YYCache 提供了三种控制维度,分别是:cost、count、age。这已经满足了绝大多数开发者的需求,我们在自己设计缓存时也可以根据自己的使用环境提供合适的控制方式。&/p&&h2&缓存替换策略&/h2&&p&在上文解析 YYCache 源码的时候,介绍了缓存替换策略的概念并且列举了很多经典的策略。YYCache 使用了双向链表(&code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code&)实现了 LRU(least-recently-used) 策略,旨在提高 YYCache 的缓存命中率。&/p&&h2&缓存命中率&/h2&&p&这一概念是在上文解析 &code&_YYLinkedMapNode&/code& 与 &code&_YYLinkedMap&/code& 小节介绍的,我们在自己设计缓存时不一定非要使用 LRU 策略,可以根据我们的实际使用环境选择最适合我们自己的缓存替换策略。&/p&&h2&性能&/h2&&p&其实性能这个东西是隐而不见的,又是到处可见的(笑)。它从我们最开始设计一个缓存架构时就被带入,一直到我们具体的实现细节中慢慢成形,最后成为了我们设计出来的缓存优秀与否的决定性因素。&/p&&p&上文中剖析了太多 YYCache 中对于性能提升的实现细节:&/p&&ul&&li&异步释放缓存对象&/li&&li&锁的选择&/li&&li&使用 NSMapTable 单例管理的 YYDiskCache&/li&&li&YYKVStorage 中的 &code&_dbStmtCache&/code&&/li&&li&甚至使用 CoreFoundation 来换取微乎其微的性能提升&/li&&/ul&&p&看到这里是不是恍然大悟,性能是怎么来的?就是这样对于每一个细节的极致追求一点一滴积少成多抠出来的。&/p&&h2&总结&/h2&&ul&&li&文章系统的解读了 YYCache 源码,相信可以让各位读者对 YYCache 的整体架构有一个清晰的认识。&/li&&li&文章结合作者 &a href=&http://link.zhihu.com/?target=https%3A//blog.ibireme.com//yycache/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&YYCache 设计思路&/a& 中的内容对 YYCache 具体功能点实现源码做了深入剖析,再用我自己的理解表述出来,希望可以对读者理解 YYCache 中具体功能的实现提供帮助。&/li&&li&根据我自己的源码理解,把我认为做的不错的提升性能的源码细节单独拎出来做出详细分析。&/li&&li&总结归纳出“一个优秀缓存需要具备哪些特质?”这一问题的答案,希望大家在面试中如果被问及“如何设计一个缓存”这类问题时可以游刃有余。额,至少可以为大家提供一些回答思路,抛砖引玉(笑)。&/li&&/ul&&p&文章写得比较用心(是我个人的原创文章,转载请注明 &a href=&http://link.zhihu.com/?target=https%3A//lision.me/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&lision.me/&/span&&span class=&invisible&&&/span&&/a&),如果发现错误会优先在我的 &a href=&http://link.zhihu.com/?target=https%3A//lision.me/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&个人博客&/a& 中更新,也推荐大家去那里与我交流(嘛~ 貌似我还没有开放评论 )。&/p&&p&希望我的文章能为你带来价值~ 也希望可以动动手指帮我分享出去 &/p&
作者: 掘金主页: 前言iOS 开发中总会用到各种缓存,但是各位有没有考虑过什么样的缓存才能被叫做优秀的缓存,或者说优秀的缓存应该具备哪些特质?闭上眼睛,想一想如果面试官让你设计一个缓存你会怎么回答?本文将结合 YYCache 的…
&figure&&img src=&https://pic1.zhimg.com/v2-f5bc200ab_b.jpg& data-rawwidth=&500& data-rawheight=&235& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&https://pic1.zhimg.com/v2-f5bc200ab_r.jpg&&&/figure&&h2&&b&1. 后台开发工程师(深圳)&/b&&/h2&&p&工作职责:&/p&&ul&&li&本科及以上学历,两年以上工作经验;&/li&&li&精通C++编程语言,有丰富的linux项目开发经历;&/li&&li&熟练掌握TCP/IP网络编程,熟悉互联网应用协议,扎实的操作系统\数据结构\算法基础;&/li&&li&有良好的问题分析、逻辑推理能力。善于解决疑难问题;&/li&&li&对mysql、redis等数据库存储实现有深入了解;&/li&&li&掌握python\php\go等编程语言加分;&/li&&li&有大数据处理、机器学习算法理论基础加分。&/li&&/ul&&p&工作要求:&/p&&ul&&li&本科及以上学历;&/li&&li&2年以上Unix/Linux操作系统下的C/C++项目的开发经验;&/li&&li&熟悉网络编程,熟悉Linux下的mysql开发;&/li&&li&精通TCP/IP协议及编程,熟悉互联网应用协议;&/li&&li&熟悉面向对象的大型分布式系统设计与开发,了解中间件的技术以及基于中间件的开发模式;&/li&&li&全面的软件知识结构(操作系统、软件工程、设计模式、数据结构、数据库系统、网络安全);&/li&&li&具备良好的分析解决问题能力,能独立承担任务和有系统进度把控能力;&/li&&li&责任心强,良好的对外沟通和团队协作能力,主动,好学;&/li&&li&具备mysql数据库优化经验、大容量通信系统开发经验者优先考虑。&/li&&/ul&&h2&&b&2. Android开发工程师(深圳)&/b&&/h2&&p&&b&工作职责:&/b& &/p&&ul&&li&负责部门Android端应用的架构设计;&/li&&li&负责部门Android端现有功能的维护及新功能模块的开发;&/li&&li&负责定位并解决现有模块存在的问题;&/li&&li&负责承担版本的系统设计、性能及内存调优工作。&/li&&/ul&&p&&b&基本要求:&/b&&/p&&ul&&li&本科及以上学历,计算机或相关专业;&/li&&li&2年以上相关工作经验;&/li&&li&熟悉android手机平台开发,有1年以上的应用程序开发经验;&/li&&li&精通C/C++或JAVA;&/li&&li&基础扎实,精通常用数据结构与算法;&/li&&li&熟悉TCP/IP协议和网络编程相关知识者优先。&/li&&/ul&&h2&&b&3. iOS开发工程师(深圳)&/b&&/h2&&p&&b&工作职责:&/b&&/p&&ul&&li&负责部门iOS终端产品的软件架构设计与开发;&/li&&li&负责对既有iOS终端应用模块优化。&/li&&/ul&&p&&b&工作要求:&/b&&/p&&ul&&li&本科以上学历;&/li&&li&2年以上手机智能终端软件开发经验,1年iOS以上平台开发经验;&/li&&li&熟悉相应架构,同时有C++与Java开发经验犹佳;&/li&&li&熟悉Linux编程,TCP/IP和无线通讯协议,有手机终端软件设计和规划能力;&/li&&li&对新技术持有敏感性以及愿意致力于新技术的探索和研究;&/li&&li&自学能力强,沟通协作意识良好,能够承担工作压力。&/li&&/ul&&p&&br&&/p&&p&感兴趣的朋友可将简历投递至:&b&&/b&&/p&
1. 后台开发工程师(深圳)工作职责:本科及以上学历,两年以上工作经验;精通C++编程语言,有丰富的linux项目开发经历;熟练掌握TCP/IP网络编程,熟悉互联网应用协议,扎实的操作系统\数据结构\算法基础;有良好的问题分析、逻辑推理能力。善于解决疑难问…
&p&看到这个问题,我就来了。我毕竟也是搜刮过大江南北的零食的人,所以难免遇到一些貌不惊人的零食包装。一开始,我是嫌弃的,不过我渐渐发现,有些包装艳丽的零食华而不实,朴实无华的零食却在味道上脱颖而出,于是我就彻底拥抱了它们。&/p&&p&&br&&/p&&blockquote&推荐说明&br&1、本文出现的所有零食附带的淘口令是为了方便大家购买,我挑选的链接也是由价低与销量高两个指标挑选出来的,只为造福群众。&br&2、淘口令用法为:复制整条淘口令(包括两边的?)-打开手机淘宝app,即可跳转至商品页面&/blockquote&&p&&br&&/p&&p&&b&子弟薯片&/b&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c7cf8a20cb6a32e}

我要回帖

更多关于 ios11 导航栏透明 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信