-
Notifications
You must be signed in to change notification settings - Fork 0
/
db.json
1 lines (1 loc) · 325 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"2.2.0"},"models":{"Asset":[{"_id":"themes/hueman1/source/css/style.styl","path":"css/style.styl","modified":0,"renderable":1},{"_id":"themes/hueman1/source/js/insight.js","path":"js/insight.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/js/main.js","path":"js/main.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/css/images/logo.png","path":"css/images/logo.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/css/images/opacity-10.png","path":"css/images/opacity-10.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/css/images/s-left.png","path":"css/images/s-left.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/css/images/thumb-default-small.png","path":"css/images/thumb-default-small.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/css/images/thumb-default.png","path":"css/images/thumb-default.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/source-code-pro/styles.css","path":"libs/source-code-pro/styles.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/styles.css","path":"libs/titillium-web/styles.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/css/font-awesome.css","path":"libs/font-awesome/css/font-awesome.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/css/font-awesome.min.css","path":"libs/font-awesome/css/font-awesome.min.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.min.css","path":"libs/lightgallery/css/lg-fb-comment-box.min.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.css.map","path":"libs/lightgallery/css/lg-fb-comment-box.css.map","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.css","path":"libs/lightgallery/css/lg-fb-comment-box.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.css","path":"libs/lightgallery/css/lg-transitions.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.css","path":"libs/lightgallery/css/lightgallery.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.css.map","path":"libs/lightgallery/css/lg-transitions.css.map","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.css.map","path":"libs/lightgallery/css/lightgallery.css.map","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.min.css","path":"libs/lightgallery/css/lightgallery.min.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.min.css","path":"libs/lightgallery/css/lg-transitions.min.css","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.eot","path":"libs/lightgallery/fonts/lg.eot","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.ttf","path":"libs/lightgallery/fonts/lg.ttf","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/img/video-play.png","path":"libs/lightgallery/img/video-play.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/img/vimeo-play.png","path":"libs/lightgallery/img/vimeo-play.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.svg","path":"libs/lightgallery/fonts/lg.svg","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.woff","path":"libs/lightgallery/fonts/lg.woff","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/img/loading.gif","path":"libs/lightgallery/img/loading.gif","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/img/youtube-play.png","path":"libs/lightgallery/img/youtube-play.png","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-autoplay.min.js","path":"libs/lightgallery/js/lg-autoplay.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-fullscreen.js","path":"libs/lightgallery/js/lg-fullscreen.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-autoplay.js","path":"libs/lightgallery/js/lg-autoplay.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-hash.js","path":"libs/lightgallery/js/lg-hash.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-hash.min.js","path":"libs/lightgallery/js/lg-hash.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-pager.js","path":"libs/lightgallery/js/lg-pager.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-fullscreen.min.js","path":"libs/lightgallery/js/lg-fullscreen.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-share.js","path":"libs/lightgallery/js/lg-share.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-pager.min.js","path":"libs/lightgallery/js/lg-pager.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-share.min.js","path":"libs/lightgallery/js/lg-share.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-thumbnail.min.js","path":"libs/lightgallery/js/lg-thumbnail.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-thumbnail.js","path":"libs/lightgallery/js/lg-thumbnail.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-video.min.js","path":"libs/lightgallery/js/lg-video.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-video.js","path":"libs/lightgallery/js/lg-video.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-zoom.min.js","path":"libs/lightgallery/js/lg-zoom.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-zoom.js","path":"libs/lightgallery/js/lg-zoom.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lightgallery.js","path":"libs/lightgallery/js/lightgallery.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/lightgallery/js/lightgallery.min.js","path":"libs/lightgallery/js/lightgallery.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasD9V_2ngZ8dMf8fLgjYEouxg.woff2","path":"libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasD9V_2ngZ8dMf8fLgjYEouxg.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasDy2Q8seG17bfDXYR_jUsrzg.woff2","path":"libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasDy2Q8seG17bfDXYR_jUsrzg.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcSo_WB_cotcEMUw1LsIE8mM.woff2","path":"libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcSo_WB_cotcEMUw1LsIE8mM.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr4-67659ICLY8bMrYhtePPA.woff2","path":"libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr4-67659ICLY8bMrYhtePPA.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcZSnX671uNZIV63UdXh3Mg0.woff2","path":"libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcZSnX671uNZIV63UdXh3Mg0.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr46gJz9aNFrmnwBdd69aqzY.woff2","path":"libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr46gJz9aNFrmnwBdd69aqzY.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr9INifKjd1RJ3NxxEi9Cy2w.woff2","path":"libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr9INifKjd1RJ3NxxEi9Cy2w.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr_SNRT0fZ5CX-AqRkMYgJJo.woff2","path":"libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr_SNRT0fZ5CX-AqRkMYgJJo.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.eot","path":"libs/font-awesome/fonts/fontawesome-webfont.eot","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.woff2","path":"libs/font-awesome/fonts/fontawesome-webfont.woff2","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.woff","path":"libs/font-awesome/fonts/fontawesome-webfont.woff","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/FontAwesome.otf","path":"libs/font-awesome/fonts/FontAwesome.otf","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/jquery/2.0.3/jquery.min.js","path":"libs/jquery/2.0.3/jquery.min.js","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.ttf","path":"libs/font-awesome/fonts/fontawesome-webfont.ttf","modified":0,"renderable":1},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.svg","path":"libs/font-awesome/fonts/fontawesome-webfont.svg","modified":0,"renderable":1}],"Cache":[{"_id":"themes/hueman1/.gitignore","hash":"cd089ae45ce870c45e434019e8f1ed4f066cd425","modified":1486549355000},{"_id":"themes/hueman1/README.md","hash":"defb1f667677e1fd13b3a4799a6c70eae621f796","modified":1486549355000},{"_id":"themes/hueman1/ko.yml","hash":"81ea44ecda87a4398bb6d88a3b02f6b73c9a1637","modified":1486549355000},{"_id":"themes/hueman1/_config.yml","hash":"241ae064e1bc4eb87c96c587ac69468ff2599b98","modified":1486550103000},{"_id":"themes/hueman1/LICENSE","hash":"3975b7883caeb33f61fada7c0ef4add7ab189849","modified":1486549355000},{"_id":"themes/hueman1/package.json","hash":"d4ff853568e592265c2c9a21ce358f15babec14a","modified":1486549355000},{"_id":"source/_posts/Grand Central DisPatch(GCD)概要.md","hash":"ddc0da07cc608057e746b546c0f05053395dd9b9","modified":1488965902000},{"_id":"source/_posts/IOS 的全屏旋转","hash":"7ad1733fdebfa40a463f807ee164e0028087143d","modified":1491536416000},{"_id":"source/_posts/Pull Request的正确姿势.md","hash":"5c175ffe7e5df706ccac18f104888f4802929553","modified":1490665707000},{"_id":"source/_posts/一种新的代码组织办法 feature flow.md","hash":"e779bde404a6ee1fff25ea64403a5cb60b690e55","modified":1487671273000},{"_id":"source/_posts/UIImageView是如何显示出来的.md","hash":"395f9efd5d5ce0478ac4a61ef7eaf275182f8069","modified":1487685322000},{"_id":"source/_posts/在继承关系的类中使用KVO引发的问题.md","hash":"cb47dff85cb1bf01fda9bc89be4088123374cf07","modified":1490665295000},{"_id":"source/_posts/几种典型的耦合与解决方法.md","hash":"dd558864d9c9f24e6fe7c260d2d577dab69c3cba","modified":1488244970000},{"_id":"source/_posts/直播音频小库 voiceLive.js.md","hash":"c15c631a7aab5445fb4e0e1182efb482fbf441d4","modified":1487670863000},{"_id":"source/_posts/降低微信页面下拉露底(橡皮筋效果)的几率.md","hash":"f264f3c6f8f4b6b100712814ac6c909265ae7ad1","modified":1488392064000},{"_id":"source/_posts/纵览 Cocoa 的 Model-View-Controller 模式.md","hash":"52f29d65cfa562a04428e805e542dc9af2e3dec2","modified":1489375594000},{"_id":"themes/hueman1/languages/ca.yml","hash":"d082e6f9e7615556b8f6c5f1286f0db8a0db195e","modified":1486549355000},{"_id":"themes/hueman1/languages/en.yml","hash":"3416fee358d869e6abf0cca695edf8386349053f","modified":1486549355000},{"_id":"themes/hueman1/languages/fr.yml","hash":"0624f8f7532f1312caaf4f8d498aab69c80a92f2","modified":1486549355000},{"_id":"themes/hueman1/languages/id.yml","hash":"03e45cf88b69e657a340362e18668e443ddaae47","modified":1486549355000},{"_id":"themes/hueman1/languages/hu.yml","hash":"5dd987ad5f33748bcba1d9a7eb78c1800b7c8bed","modified":1486549355000},{"_id":"themes/hueman1/languages/es.yml","hash":"97191c8ab7ce5334369d96f6e53a6d365a2057b1","modified":1486549355000},{"_id":"themes/hueman1/languages/pt-BR.yml","hash":"6f9539673e08d66866bb210b78863ef68023f991","modified":1486549355000},{"_id":"themes/hueman1/languages/ja.yml","hash":"56f3af0bf5cc56f9f7bf24fe5cb881a6a1b34e7b","modified":1486549355000},{"_id":"themes/hueman1/languages/ru.yml","hash":"99f111b39f867d421ff4cb859dd1deb26caa382e","modified":1486549355000},{"_id":"themes/hueman1/languages/tr.yml","hash":"003cb50200eba865d57e5c53925636f1a5ec0a70","modified":1486549355000},{"_id":"themes/hueman1/languages/zh-CN.yml","hash":"01297df999b98de8e097f3049c5fa7746b79b247","modified":1486549355000},{"_id":"themes/hueman1/languages/vi.yml","hash":"edeb4492a08af458fe958dc4d48101c96750b159","modified":1486549355000},{"_id":"themes/hueman1/layout/archive.ejs","hash":"8785477232088e09a75da88a0cdcb32fedf4f81f","modified":1486549355000},{"_id":"themes/hueman1/layout/category.ejs","hash":"5096d3f019098d9940429152295f6d6161d887ba","modified":1486549355000},{"_id":"themes/hueman1/layout/index.ejs","hash":"8785477232088e09a75da88a0cdcb32fedf4f81f","modified":1486549355000},{"_id":"themes/hueman1/layout/page.ejs","hash":"5afddd6a45fa72beacec8d760487dfe8a667e622","modified":1486549355000},{"_id":"themes/hueman1/layout/post.ejs","hash":"5afddd6a45fa72beacec8d760487dfe8a667e622","modified":1486549355000},{"_id":"themes/hueman1/layout/layout.ejs","hash":"35fd7fe6d4e2d313246a0a31446a597714facdea","modified":1486549355000},{"_id":"themes/hueman1/layout/tag.ejs","hash":"8785477232088e09a75da88a0cdcb32fedf4f81f","modified":1486549355000},{"_id":"themes/hueman1/scripts/excerpt.js","hash":"630f17f2647b2ba5b207dba36fb3c6587a3aa04f","modified":1486549355000},{"_id":"themes/hueman1/scripts/meta.js","hash":"1993754a2f3dffa283fa0538eb8f056385b69ad4","modified":1486549355000},{"_id":"themes/hueman1/scripts/thumbnail.js","hash":"e78b1b978fa1acad5409afc685d13f669e80b24f","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/counter.ejs","hash":"9c41bc718322ba0e4e08c301f15fae720c995b2e","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/disqus.ejs","hash":"83158f5b00f95acf847524fd7ffeb5aab25add78","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/duoshuo.ejs","hash":"ce46d7410a99b57704da32e9d09071cef6c9fa93","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/facebook.ejs","hash":"6407d3d6823503d043935b0bc16fa5f61aa9e4dc","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/isso.ejs","hash":"4f8b81ff5bb418ec11ce080d515f247bfe436014","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/index.ejs","hash":"1ecc5ba82ae4236da7635a58d4584fd67f6f1fc3","modified":1486549355000},{"_id":"themes/hueman1/layout/comment/scripts.ejs","hash":"305aa07646ab03e00d8239a811f4ec6f75751e1e","modified":1486549355000},{"_id":"themes/hueman1/layout/common/content-title.ejs","hash":"43288ebc0ea30d80e6d7df56f2e7ed5d7935ed1a","modified":1486549355000},{"_id":"themes/hueman1/layout/common/archive.ejs","hash":"bf909e4d6798374daf114cf41540f2bd300408d9","modified":1486549355000},{"_id":"themes/hueman1/layout/common/article.ejs","hash":"47193f38ed50a2f2d7ecf90489eaa03022a747cb","modified":1487671396000},{"_id":"themes/hueman1/layout/comment/youyan.ejs","hash":"6fe807992832939caf6c3e7651d052df9520d88e","modified":1486549355000},{"_id":"themes/hueman1/layout/common/head.ejs","hash":"dacc29256a51d4867c662b6f1805d0d7652de45d","modified":1486549355000},{"_id":"themes/hueman1/layout/common/footer.ejs","hash":"4d01bd3d28820936b0d09529b60e9d6930afc9fe","modified":1486549355000},{"_id":"themes/hueman1/layout/common/header.ejs","hash":"4dbb53393c3ebef01088592babf135d2f6559f47","modified":1486549355000},{"_id":"themes/hueman1/layout/common/scripts.ejs","hash":"db677ab336d31292bffc071b2115e25e9c6f198d","modified":1486549355000},{"_id":"themes/hueman1/layout/common/sidebar.ejs","hash":"745666ee78b714aa34d8774a005a0598261ec349","modified":1486549355000},{"_id":"themes/hueman1/layout/common/thumbnail.ejs","hash":"c5fba5d5287f49e02040c530cd92312b2221a2c1","modified":1486549355000},{"_id":"themes/hueman1/layout/plugin/baidu-analytics.ejs","hash":"d99089976258050666208f29000f84496fe1029c","modified":1486549355000},{"_id":"themes/hueman1/layout/plugin/google-analytics.ejs","hash":"349f08b6521a16e79046b1f94f04317ac74f556e","modified":1486549355000},{"_id":"themes/hueman1/layout/common/summary.ejs","hash":"bebaa0de6bae6130fcff15ccdfb2a8d50a409768","modified":1487671633000},{"_id":"themes/hueman1/layout/plugin/scripts.ejs","hash":"52552496d83d032702f9fc40da8d62894dc51dae","modified":1486549355000},{"_id":"themes/hueman1/layout/search/baidu.ejs","hash":"bcffa60f2d1750ac7499e928f538176e3804393b","modified":1486549355000},{"_id":"themes/hueman1/layout/search/index.ejs","hash":"1a6a742727018567f60f8815be0bff5a45294ce5","modified":1486549355000},{"_id":"themes/hueman1/layout/search/insight.ejs","hash":"130fe3d33ac71da0b50f7fee6a87979f30938a1b","modified":1486549355000},{"_id":"themes/hueman1/layout/search/swiftype.ejs","hash":"379e66d2c13526e72e4120c443f95fccf4edef71","modified":1486549355000},{"_id":"themes/hueman1/layout/share/addtoany.ejs","hash":"ac180c4c84b73a04d61b17e7dc18c257e20bf59f","modified":1486549355000},{"_id":"themes/hueman1/layout/share/jiathis.ejs","hash":"21ebaa51e828cba2cefbeeaccb01514643565755","modified":1486549355000},{"_id":"themes/hueman1/layout/share/bdshare.ejs","hash":"a1e772c5a6f174d585b0c1e574058f75dc8e2898","modified":1486549355000},{"_id":"themes/hueman1/layout/share/index.ejs","hash":"029e91aace5a4c0d8387fc7744c477ccc6865c30","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/archive.ejs","hash":"c4d303eaaa23768e52ead324c422a8900b1fe448","modified":1486549355000},{"_id":"themes/hueman1/layout/share/default.ejs","hash":"7492f5b375a56c67a1a1a4f6b893e37f49dc86dc","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/category.ejs","hash":"2d705df76f2eef7d695a971266fc104e89ca6bcd","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/sticky_posts.ejs","hash":"6bad4126bed652f5f9e93027cbe3ee03b67b034a","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/links.ejs","hash":"97dab84d6336a4c926ddc288d5a6c264f54c50c3","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/recent_posts.ejs","hash":"80ae76951be06e937f9fab41edfc40b101e82f58","modified":1487671815000},{"_id":"themes/hueman1/layout/widget/tagcloud.ejs","hash":"3ecb048d6098bc3953043a4c25f1f7c4b23397cf","modified":1486549355000},{"_id":"themes/hueman1/layout/widget/tag.ejs","hash":"bfbc63e675439dcdc35e07dce6948e41500b649c","modified":1486549355000},{"_id":"themes/hueman1/source/css/.DS_Store","hash":"495dc0d217dbc3e7cb0c015a4aa9af3e7011d37f","modified":1486454296000},{"_id":"themes/hueman1/source/css/_extend.styl","hash":"2860d503b68ae81ea7675c82c6e63873e0c9e761","modified":1486549355000},{"_id":"themes/hueman1/source/css/_responsive.styl","hash":"4addaf2f203563accf0479850eda1ab4f4a4c34b","modified":1486549356000},{"_id":"themes/hueman1/source/css/_variables.styl","hash":"ce03de3658b55b5f90bb4dd18679be18ec38acaa","modified":1486549356000},{"_id":"themes/hueman1/source/css/style.styl","hash":"b76d43839bda01ee791e71093f009ad2c8b093b2","modified":1486549356000},{"_id":"themes/hueman1/source/js/insight.js","hash":"6ee84c42c2b230ff9e9bf605a444bd671d44f9e3","modified":1486549356000},{"_id":"themes/hueman1/source/js/main.js","hash":"ebe5a326605ab655155235c532b54a78a0662988","modified":1486549356000},{"_id":"themes/hueman1/layout/common/post/author.ejs","hash":"b4843b52f5701c2a2ea828549bf1ee703e9ed55b","modified":1487671482000},{"_id":"themes/hueman1/layout/common/post/category.ejs","hash":"f47522c059d3b4d721ce501203c05f94a285c0b5","modified":1486549355000},{"_id":"themes/hueman1/layout/common/post/date.ejs","hash":"2d090d33cbd23129d63a0ba768a03d717399d9c5","modified":1486549355000},{"_id":"themes/hueman1/layout/common/post/gallery.ejs","hash":"659f019761116313169148ec61773e7b84abb739","modified":1486549355000},{"_id":"themes/hueman1/layout/common/post/nav.ejs","hash":"c5f41ebf451cff39eaf116096604ce706a175767","modified":1486549355000},{"_id":"themes/hueman1/layout/common/post/title.ejs","hash":"6d19c61afb1f5f71c483be2ce37c6820ac2cd8b5","modified":1486549355000},{"_id":"themes/hueman1/layout/common/post/tag.ejs","hash":"a364204d7152adf6c7522b6568990437e8308d1f","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/agate.styl","hash":"601eb70448a16b918df132f6fc41e891ae053653","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/androidstudio.styl","hash":"65d09f1b0e81c6a182f549fd3de51e59823c97ae","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/arduino-light.styl","hash":"15e8572585cd708221c513dea4bdd89d8fe56c10","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/arta.styl","hash":"1a5accc115f41d1b669ed708ac6a29abac876599","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/ascetic.styl","hash":"32cff3bef6fac3760fe78f203096477052a90552","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-dune-dark.styl","hash":"df50a85a4b14c7ca6e825d665594b91229d0e460","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-cave-light.styl","hash":"a5be0744a7ecf4a08f600ade4cfd555afc67bc15","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-dune-light.styl","hash":"931435fbc6f974e8ce9e32722680035d248a9dc1","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-cave-dark.styl","hash":"bc647b2c1d971d7cc947aa1ed66e9fd115261921","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-estuary-dark.styl","hash":"d84382bc8298f96730757391d3e761b7e640f406","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-estuary-light.styl","hash":"344276ca9b27e51d4c907f76afe5d13cf8e60bdf","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-forest-dark.styl","hash":"57c154c6045a038dc7df0a25927853e10bf48c4a","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-heath-dark.styl","hash":"b0cf13b2233e7bc38342032d2d7296591a4c2bcf","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-forest-light.styl","hash":"95228d9f2102fad425536aac44b80b2cba1f5950","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-heath-light.styl","hash":"8c8c2e445abef85273be966d59770e9ced6aac21","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-lakeside-dark.styl","hash":"bb0a8c4ad0dd8e3e7de7122ddf268fc42aa94acb","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-lakeside-light.styl","hash":"2c54cb9bdb259ae3b5b29f63ac2469ed34b08578","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-plateau-dark.styl","hash":"09c64f1a7052aec9070c36c0431df25216afaea1","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-plateau-light.styl","hash":"d1a05fdd1ededc9063d181ab25bad55a164aeb4a","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-savanna-dark.styl","hash":"a16c919a1ccf2f845488078fb341381bec46b1f3","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-savanna-light.styl","hash":"f8244c93711c7cb59dd79d2df966806b30d171ea","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-seaside-dark.styl","hash":"ce233a101daea7124cbfcd34add43ccfe2e1e1c7","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-sulphurpool-light.styl","hash":"efa52713efc468abeeb2b9299704371583b857de","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-sulphurpool-dark.styl","hash":"414b0cfc142f70afe359c16450b651e28bf7325a","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/brown-paper.styl","hash":"c2326ba20a5020a66ca7895258d18833327d4334","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/atelier-seaside-light.styl","hash":"0597342da6e2d0c5bdcc7d42dabb07322b1a4177","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/codepen-embed.styl","hash":"f4dcc84d8e39f9831a5efe80e51923fc3054feb0","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/brown-papersq.png","hash":"3a1332ede3a75a3d24f60b6ed69035b72da5e182","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/dark.styl","hash":"71ce56d311cc2f3a605f6e2c495ccd7236878404","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/docco.styl","hash":"b1c176378bb275f2e8caa759f36294e42d614bf1","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/color-brewer.styl","hash":"2a439d6214430e2f45dd4939b4dfe1fe1a20aa0f","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/darkula.styl","hash":"ad0d5728d21645039c9f199e7a56814170ed3bab","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/far.styl","hash":"d9928010ffe71e80b97a5afcba1a4975efdd7372","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/foundation.styl","hash":"bf8ddc94b4ad995b8b8805b5a4cf95004553fdac","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/github-gist.styl","hash":"48211a03d33e7f7ada0b261162bea06676155a71","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/github.styl","hash":"3336aeba324c6d34a6fd41fef9b47bc598f7064c","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/googlecode.styl","hash":"bda816beee7b439814b514e6869dc678822be1bc","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/grayscale.styl","hash":"bf37d8b8d1e602126c51526f0cc28807440228ed","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/hopscotch.styl","hash":"b374c6550b89b4751aedc8fbc3cf98d95bd70ead","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/highlightjs.styl","hash":"0e198b7a59191c7a39b641a4ddd22c948edb9358","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/hybrid.styl","hash":"ea8d7ddc258b073308746385f5cb85aabb8bfb83","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/idea.styl","hash":"a02967cb51c16a34e0ee895d33ded2b823d35b21","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/index.styl","hash":"d421ed06c84f7a561b293f662a670bf132d41c63","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/ir-black.styl","hash":"693078bbd72a2091ed30f506cc55949600b717af","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/magula.styl","hash":"16d323f989b1420a0f72ef989242ece9bf17a456","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/kimbie.dark.styl","hash":"45dbb168f22d739d0109745d2decd66b5f94e786","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/mono-blue.styl","hash":"4c89a6ae29de67c0700585af82a60607e85df928","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/monokai-sublime.styl","hash":"25aa2fc1dbe38593e7c7ebe525438a39574d9935","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/kimbie.light.styl","hash":"61f8baed25be05288c8604d5070afbcd9f183f49","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/monokai.styl","hash":"5a4fe9f957fd7a368c21b62a818403db4270452f","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/paraiso-light.styl","hash":"d224d1df0eb3395d9eea1344cee945c228af2911","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/pojoaque.jpg","hash":"c5fe6533b88b21f8d90d3d03954c6b29baa67791","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/paraiso-dark.styl","hash":"f1537bd868579fa018ecdbfd2eb922dcf3ba2cac","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/pojoaque.styl","hash":"77dae9dc41945359d17fe84dbd317f1b40b2ee33","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/rainbow.styl","hash":"ce73b858fc0aba0e57ef9fb136c083082746bc1d","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/obsidian.styl","hash":"55572bbcfee1de6c31ac54681bb00336f5ae826d","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/railscasts.styl","hash":"acd620f8bb7ff0e3fe5f9a22b4433ceef93a05e6","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/school-book.png","hash":"711ec983c874e093bb89eb77afcbdf6741fa61ee","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/solarized-light.styl","hash":"aa0dd3fd25c464183b59c5575c9bee8756b397f2","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/solarized-dark.styl","hash":"702b9299a48c90124e3ac1d45f1591042f2beccc","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/school-book.styl","hash":"d43560fe519a931ce6da7d57416d7aa148441b83","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/sunburst.styl","hash":"a0b5b5129547a23865d400cfa562ea0ac1ee3958","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/tomorrow-night-blue.styl","hash":"8b3087d4422be6eb800935a22eb11e035341c4ba","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/tomorrow-night-eighties.styl","hash":"fa57b3bb7857a160fc856dbe319b31e30cc5d771","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/tomorrow-night.styl","hash":"19b3080d4b066b40d50d7e7f297472482b5801fd","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/tomorrow.styl","hash":"15779cf6846725c7c35fc56cac39047d7e0aec1c","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/vs.styl","hash":"959a746f4b37aacb5d1d6ff1d57e0c045289d75d","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/tomorrow-night-bright.styl","hash":"0ac6af6ecb446b5b60d6226748e4a6532db34f57","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/xcode.styl","hash":"5e8532ae8366dcf6a4ef5e4813dc3d42ab3d0a50","modified":1486549355000},{"_id":"themes/hueman1/source/css/_highlight/zenburn.styl","hash":"fc5ec840435dad80964d04519d3f882ddc03746a","modified":1486549355000},{"_id":"themes/hueman1/source/css/_partial/archive.styl","hash":"5e5fb791ab54f0acf33850f586f7aa8cb2782f3a","modified":1486549355000},{"_id":"themes/hueman1/source/css/_partial/article.styl","hash":"0d836e162fc5b933a0844945d8f9a7f33393bc62","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/assets.styl","hash":"3d95417663c5a737f064a31ab4ef52bac7fda8df","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/footer.styl","hash":"8dffae4ac6a57e506e378a9ce44b85a15a912cad","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/insight.styl","hash":"3d66323e7b75ad197e80d7189a8d9216e1e1ef2f","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/header.styl","hash":"f94fcb4e2cbda45c8dc910ddb8ff4f19ff0644bf","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/comment.styl","hash":"d2de8f2c1cf6236ead0800c2a1566e01e7ae0b44","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/nav.styl","hash":"587a9c9d304ed83eb0331a1d16693461805311c3","modified":1486549356000},{"_id":"themes/hueman1/source/css/_partial/sidebar.styl","hash":"1e6255f1bfa1a820f70d5cfdadf2caa726bf853d","modified":1486549356000},{"_id":"themes/hueman1/source/css/images/logo.png","hash":"6cbbc1a71f4e7b9002354ad1215333f231d1003c","modified":1486549356000},{"_id":"themes/hueman1/source/css/images/opacity-10.png","hash":"bbc979866c5b50e8adb348419154b28b1ff44d78","modified":1486549356000},{"_id":"themes/hueman1/source/css/images/.DS_Store","hash":"df2fbeb1400acda0909a32c1cf6bf492f1121e07","modified":1486460291000},{"_id":"themes/hueman1/source/css/images/s-left.png","hash":"c8cac4f4e3492606fab93196364bd0f87d93bb98","modified":1486549356000},{"_id":"themes/hueman1/source/css/images/thumb-default-small.png","hash":"e8403b97ed9251f9f5207765b0ce796c5000b4ba","modified":1486549356000},{"_id":"themes/hueman1/source/css/images/thumb-default.png","hash":"2d0ba175d958d342494241c616a74d37f48059fb","modified":1486549356000},{"_id":"themes/hueman1/source/libs/source-code-pro/styles.css","hash":"93c308012738728f906cd4c5cfdb34189e0c712b","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/styles.css","hash":"d98f0c50aae4c922cd0b663fa820fd7dff2dd9b1","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/css/font-awesome.css","hash":"b5020c3860669185ba3f316fa7332cdf5c06f393","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/css/font-awesome.min.css","hash":"7cd5a3384333f95c3d37d9488ad82cd6c4b03761","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.min.css","hash":"05830fadb8454f39dcc98c8686eb4d5c24b71fc0","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.css.map","hash":"51e9df39edf0faa3f38c1bab0c1fa6c922b9edcb","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-fb-comment-box.css","hash":"844ce27b8488968bccb3e50bb49184ba2aae0625","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.css","hash":"7871c28498d74451d6aa438c8d3a1817810a1e19","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.css","hash":"bef55316a32e512d5a8940e5d0bfe8bf7a9c5c61","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.css.map","hash":"50c3348638b4d82fa08a449c690e8d2bb593005d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.css.map","hash":"3175b4107078674d25798979f7666f4daf31e624","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lightgallery.min.css","hash":"c9a2e19c932b56f4a2ce30c98910d10b74edb38a","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/css/lg-transitions.min.css","hash":"5c22e2073a4c96d6212c72135391b599e8d1359f","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.eot","hash":"54caf05a81e33d7bf04f2e420736ce6f1de5f936","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.ttf","hash":"f6421c0c397311ae09f9257aa58bcd5e9720f493","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/img/video-play.png","hash":"3ea484cdc04d2e4547f80cbf80001dcf248c94ef","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/img/vimeo-play.png","hash":"6190254f2804904a4a1fa1eb390dfd334e416992","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.svg","hash":"9a732790adc004b22022cc60fd5f77ec4c8e3e5a","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/fonts/lg.woff","hash":"3048de344dd5cad4624e0127e58eaae4b576f574","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/img/loading.gif","hash":"607810444094b8619fa4efa6273bc2a7e38dd4b4","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/img/youtube-play.png","hash":"fea6df9d9d43151f9c9d15f000adb30eb3e26fc4","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-autoplay.min.js","hash":"d845741bcaf961579622880eb2a445257efad1ac","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-fullscreen.js","hash":"65c47ac65362854ba44b00a010bb01e3630209d8","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-autoplay.js","hash":"426bb78b93acfc39d533ea2bab1cec8dc289cf24","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-hash.js","hash":"15d16516c5642d3de1566ff8fc9160136ccaa405","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-hash.min.js","hash":"43f1e1e720ab0e241c19b83aa26bd6848eab8edc","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-pager.js","hash":"8092c692b244bb26343eb03b91bd97deb9dafc9c","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-fullscreen.min.js","hash":"b6b9e4022700b7faf2a5a175ba44a3bd938fdd20","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-share.js","hash":"b7fb5f6474911060a351b0a6fe9dbb9ac3fb22aa","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-pager.min.js","hash":"25caa6ff65b1c6dee09941e795ae2633bdbab211","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-share.min.js","hash":"39c615f07c5d3aaa65a2c3068a30fdd6dd5c372d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-thumbnail.min.js","hash":"18dd7d2909d1bfd6852f031d03e774b4428c512b","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-thumbnail.js","hash":"3a6476b6df1d2bef4a21861a78776282a7a11ef1","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-video.min.js","hash":"032c001ab045a69856f9c3ed4a2a3bf12a8e310f","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-video.js","hash":"4f99b598f6bb18de9eca8c45c5b4373a03962367","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-zoom.min.js","hash":"15b49f9728439819ece15e4295cce254c87a4f45","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lg-zoom.js","hash":"a758e2c8fcf710f9ff761da0eea0ab9321f3484d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lightgallery.js","hash":"3cd19b33ba99efd5ba1d167da91720566d274b2c","modified":1486549356000},{"_id":"themes/hueman1/source/libs/lightgallery/js/lightgallery.min.js","hash":"956ef9b706755318da69ad0b5d7786339d831251","modified":1486549356000},{"_id":"themes/hueman1/source/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasD9V_2ngZ8dMf8fLgjYEouxg.woff2","hash":"942addaec4d3a60af33947a84a3d85f926015947","modified":1486549356000},{"_id":"themes/hueman1/source/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasDy2Q8seG17bfDXYR_jUsrzg.woff2","hash":"b0e0bb5ef78db8b15d430d0b9be9d4329289a310","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcSo_WB_cotcEMUw1LsIE8mM.woff2","hash":"6d17eac7fcc2866f10d1f2725a08ab749a6e978d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr4-67659ICLY8bMrYhtePPA.woff2","hash":"4e5557954ec161edc03b6f971ddefee6179c1305","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcZSnX671uNZIV63UdXh3Mg0.woff2","hash":"78029561e4c2ec565ea11c3f5bbd052b018af8a6","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr46gJz9aNFrmnwBdd69aqzY.woff2","hash":"1454a4753468b607c23deac9f5438cd0ed5cb35d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr9INifKjd1RJ3NxxEi9Cy2w.woff2","hash":"1758c64c8acec4497735ccb5336b1a518d24024c","modified":1486549356000},{"_id":"themes/hueman1/source/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr_SNRT0fZ5CX-AqRkMYgJJo.woff2","hash":"e2e2993940fc54ed41f26e39257fdbd824c05e81","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.eot","hash":"965ce8f688fedbeed504efd498bc9c1622d12362","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.woff2","hash":"97e438cc545714309882fbceadbf344fcaddcec5","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.woff","hash":"6d7e6a5fc802b13694d8820fc0138037c0977d2e","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/FontAwesome.otf","hash":"1b22f17fdc38070de50e6d1ab3a32da71aa2d819","modified":1486549356000},{"_id":"themes/hueman1/source/libs/jquery/2.0.3/jquery.min.js","hash":"a6eedf84389e1bc9f757bc2d19538f8c8d1cae9d","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.ttf","hash":"61d8d967807ef12598d81582fa95b9f600c3ee01","modified":1486549356000},{"_id":"themes/hueman1/source/libs/font-awesome/fonts/fontawesome-webfont.svg","hash":"c0522272bbaef2acb3d341912754d6ea2d0ecfc0","modified":1486549356000},{"_id":"public/content.json","hash":"70cf32edf17ad7f519fae0bfa0ac66bb4b020f50","modified":1491536482512},{"_id":"public/2017/03/28/Pull Request的正确姿势/index.html","hash":"c26b055a936e6daf335be0a7e1bfe785d3c2947b","modified":1491536482912},{"_id":"public/2017/03/21/在继承关系的类中使用KVO引发的问题/index.html","hash":"4deab1e9542753228f5d43c87e60a2fb12a4348e","modified":1491536482913},{"_id":"public/2017/03/13/纵览 Cocoa 的 Model-View-Controller 模式/index.html","hash":"1147a084c931441b744c30027abb31643e6473f0","modified":1491536482913},{"_id":"public/2017/03/08/Grand Central DisPatch(GCD)概要/index.html","hash":"089b49bca51d885d300638ee4caac2cb021a9367","modified":1491536482913},{"_id":"public/2017/03/02/降低微信页面下拉露底(橡皮筋效果)的几率/index.html","hash":"67f75865f3f9af0af776fafeb70a3b8e1e39d0e8","modified":1491536482913},{"_id":"public/2017/02/28/几种典型的耦合与解决方法/index.html","hash":"9e5fa98ff9b03799598e5e5f17a46ab8cb5c2cfa","modified":1491536482913},{"_id":"public/2017/02/21/UIImageView是如何显示出来的/index.html","hash":"d408adae8359b523bda88c1814991e02f7cf4b57","modified":1491536482913},{"_id":"public/2017/02/08/一种新的代码组织办法 feature flow/index.html","hash":"4762341f5a9733d2d32e8be2620d11f1fdd4ee25","modified":1491536482914},{"_id":"public/2016/12/05/直播音频小库 voiceLive.js/index.html","hash":"cda156d88bba496c0714b40940aadd8c3ead5721","modified":1491536482914},{"_id":"public/archives/index.html","hash":"62cee481ed5370e3cf1b2bf7fbb5dd33972ee537","modified":1491536482914},{"_id":"public/archives/2016/index.html","hash":"e7ba8c8667aadfe61323cc67ad6dae8d44b8fd61","modified":1491536482914},{"_id":"public/archives/2016/12/index.html","hash":"c509e93ffff39e95cc64abd51d3cbfcbf5dadf60","modified":1491536482914},{"_id":"public/archives/2017/index.html","hash":"d2867e7d6d443c77ce871e1477df6722d2174d4d","modified":1491536482914},{"_id":"public/archives/2017/02/index.html","hash":"cb234a41ad027ba4554a63d98c9308cb6c2ddcef","modified":1491536482914},{"_id":"public/archives/2017/03/index.html","hash":"4b210428755888a06033adfa65881845e659995f","modified":1491536482914},{"_id":"public/categories/ios/index.html","hash":"9c0505cbf42ca4fcb176cad2eb82d61718b3b132","modified":1491536482914},{"_id":"public/categories/frontend/index.html","hash":"ae901f8dbfa6afe2f205472b24808952375676b1","modified":1491536482914},{"_id":"public/index.html","hash":"2fb45caec252ed4cffc6aff119e1a7fc5b446548","modified":1491536482914},{"_id":"public/tags/GCD/index.html","hash":"b35acdc264efcf0e0112d13c268ed93dadcb1158","modified":1491536482914},{"_id":"public/tags/UIImageView/index.html","hash":"04666b653a425774afeef96f84676c97b85cbdc9","modified":1491536482914},{"_id":"public/tags/feature-flow/index.html","hash":"475a3009523d8a7d7ceb265d3da2f0f2a8a6e165","modified":1491536482914},{"_id":"public/tags/live-audio/index.html","hash":"e593f4e295b2bbcdb12278e7a696370a9b00858a","modified":1491536482914},{"_id":"public/tags/preventViewScroll/index.html","hash":"2fd88fc740cadc45024832d9cc39d54aa7017655","modified":1491536482915},{"_id":"public/tags/Cocoa/index.html","hash":"322af4d33a2df99e1c3e16f8a10993d82bc1eafd","modified":1491536482915},{"_id":"public/css/images/opacity-10.png","hash":"bbc979866c5b50e8adb348419154b28b1ff44d78","modified":1491536482927},{"_id":"public/css/images/s-left.png","hash":"c8cac4f4e3492606fab93196364bd0f87d93bb98","modified":1491536482927},{"_id":"public/css/images/logo.png","hash":"6cbbc1a71f4e7b9002354ad1215333f231d1003c","modified":1491536482927},{"_id":"public/css/images/thumb-default-small.png","hash":"e8403b97ed9251f9f5207765b0ce796c5000b4ba","modified":1491536482927},{"_id":"public/css/images/thumb-default.png","hash":"2d0ba175d958d342494241c616a74d37f48059fb","modified":1491536482927},{"_id":"public/libs/lightgallery/css/lg-fb-comment-box.css.map","hash":"51e9df39edf0faa3f38c1bab0c1fa6c922b9edcb","modified":1491536482927},{"_id":"public/libs/lightgallery/css/lg-transitions.css.map","hash":"50c3348638b4d82fa08a449c690e8d2bb593005d","modified":1491536482927},{"_id":"public/libs/lightgallery/css/lightgallery.css.map","hash":"3175b4107078674d25798979f7666f4daf31e624","modified":1491536482927},{"_id":"public/libs/lightgallery/fonts/lg.eot","hash":"54caf05a81e33d7bf04f2e420736ce6f1de5f936","modified":1491536482927},{"_id":"public/libs/lightgallery/fonts/lg.ttf","hash":"f6421c0c397311ae09f9257aa58bcd5e9720f493","modified":1491536482927},{"_id":"public/libs/lightgallery/img/video-play.png","hash":"3ea484cdc04d2e4547f80cbf80001dcf248c94ef","modified":1491536482927},{"_id":"public/libs/lightgallery/img/vimeo-play.png","hash":"6190254f2804904a4a1fa1eb390dfd334e416992","modified":1491536482927},{"_id":"public/libs/lightgallery/fonts/lg.svg","hash":"9a732790adc004b22022cc60fd5f77ec4c8e3e5a","modified":1491536482927},{"_id":"public/libs/lightgallery/fonts/lg.woff","hash":"3048de344dd5cad4624e0127e58eaae4b576f574","modified":1491536482928},{"_id":"public/libs/lightgallery/img/loading.gif","hash":"607810444094b8619fa4efa6273bc2a7e38dd4b4","modified":1491536482928},{"_id":"public/libs/lightgallery/img/youtube-play.png","hash":"fea6df9d9d43151f9c9d15f000adb30eb3e26fc4","modified":1491536482928},{"_id":"public/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasD9V_2ngZ8dMf8fLgjYEouxg.woff2","hash":"942addaec4d3a60af33947a84a3d85f926015947","modified":1491536482928},{"_id":"public/libs/source-code-pro/fonts/mrl8jkM18OlOQN8JLgasDy2Q8seG17bfDXYR_jUsrzg.woff2","hash":"b0e0bb5ef78db8b15d430d0b9be9d4329289a310","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcSo_WB_cotcEMUw1LsIE8mM.woff2","hash":"6d17eac7fcc2866f10d1f2725a08ab749a6e978d","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr4-67659ICLY8bMrYhtePPA.woff2","hash":"4e5557954ec161edc03b6f971ddefee6179c1305","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/7XUFZ5tgS-tD6QamInJTcZSnX671uNZIV63UdXh3Mg0.woff2","hash":"78029561e4c2ec565ea11c3f5bbd052b018af8a6","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr46gJz9aNFrmnwBdd69aqzY.woff2","hash":"1454a4753468b607c23deac9f5438cd0ed5cb35d","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr9INifKjd1RJ3NxxEi9Cy2w.woff2","hash":"1758c64c8acec4497735ccb5336b1a518d24024c","modified":1491536482928},{"_id":"public/libs/titillium-web/fonts/anMUvcNT0H1YN4FII8wpr_SNRT0fZ5CX-AqRkMYgJJo.woff2","hash":"e2e2993940fc54ed41f26e39257fdbd824c05e81","modified":1491536482928},{"_id":"public/libs/font-awesome/fonts/fontawesome-webfont.eot","hash":"965ce8f688fedbeed504efd498bc9c1622d12362","modified":1491536483644},{"_id":"public/libs/font-awesome/fonts/fontawesome-webfont.woff2","hash":"97e438cc545714309882fbceadbf344fcaddcec5","modified":1491536483649},{"_id":"public/libs/font-awesome/fonts/fontawesome-webfont.woff","hash":"6d7e6a5fc802b13694d8820fc0138037c0977d2e","modified":1491536483649},{"_id":"public/libs/font-awesome/fonts/FontAwesome.otf","hash":"1b22f17fdc38070de50e6d1ab3a32da71aa2d819","modified":1491536483650},{"_id":"public/js/insight.js","hash":"6ee84c42c2b230ff9e9bf605a444bd671d44f9e3","modified":1491536483656},{"_id":"public/js/main.js","hash":"ebe5a326605ab655155235c532b54a78a0662988","modified":1491536483656},{"_id":"public/libs/source-code-pro/styles.css","hash":"93c308012738728f906cd4c5cfdb34189e0c712b","modified":1491536483656},{"_id":"public/libs/titillium-web/styles.css","hash":"d98f0c50aae4c922cd0b663fa820fd7dff2dd9b1","modified":1491536483656},{"_id":"public/libs/lightgallery/css/lg-fb-comment-box.min.css","hash":"05830fadb8454f39dcc98c8686eb4d5c24b71fc0","modified":1491536483656},{"_id":"public/libs/lightgallery/css/lg-fb-comment-box.css","hash":"844ce27b8488968bccb3e50bb49184ba2aae0625","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-autoplay.min.js","hash":"d845741bcaf961579622880eb2a445257efad1ac","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-fullscreen.js","hash":"65c47ac65362854ba44b00a010bb01e3630209d8","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-autoplay.js","hash":"426bb78b93acfc39d533ea2bab1cec8dc289cf24","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-hash.js","hash":"15d16516c5642d3de1566ff8fc9160136ccaa405","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-hash.min.js","hash":"43f1e1e720ab0e241c19b83aa26bd6848eab8edc","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-pager.js","hash":"8092c692b244bb26343eb03b91bd97deb9dafc9c","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-share.js","hash":"b7fb5f6474911060a351b0a6fe9dbb9ac3fb22aa","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-pager.min.js","hash":"25caa6ff65b1c6dee09941e795ae2633bdbab211","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-fullscreen.min.js","hash":"b6b9e4022700b7faf2a5a175ba44a3bd938fdd20","modified":1491536483656},{"_id":"public/libs/lightgallery/js/lg-share.min.js","hash":"39c615f07c5d3aaa65a2c3068a30fdd6dd5c372d","modified":1491536483657},{"_id":"public/libs/lightgallery/js/lg-thumbnail.min.js","hash":"18dd7d2909d1bfd6852f031d03e774b4428c512b","modified":1491536483657},{"_id":"public/libs/lightgallery/js/lg-video.js","hash":"4f99b598f6bb18de9eca8c45c5b4373a03962367","modified":1491536483657},{"_id":"public/libs/lightgallery/js/lg-zoom.min.js","hash":"15b49f9728439819ece15e4295cce254c87a4f45","modified":1491536483657},{"_id":"public/libs/lightgallery/js/lg-video.min.js","hash":"032c001ab045a69856f9c3ed4a2a3bf12a8e310f","modified":1491536483657},{"_id":"public/css/style.css","hash":"e1cfeee9d230b27488829c38302cac86b6c66e7b","modified":1491536483657},{"_id":"public/libs/font-awesome/css/font-awesome.css","hash":"b5020c3860669185ba3f316fa7332cdf5c06f393","modified":1491536483657},{"_id":"public/libs/font-awesome/css/font-awesome.min.css","hash":"7cd5a3384333f95c3d37d9488ad82cd6c4b03761","modified":1491536483657},{"_id":"public/libs/lightgallery/css/lg-transitions.css","hash":"7871c28498d74451d6aa438c8d3a1817810a1e19","modified":1491536483657},{"_id":"public/libs/lightgallery/css/lightgallery.css","hash":"bef55316a32e512d5a8940e5d0bfe8bf7a9c5c61","modified":1491536483658},{"_id":"public/libs/lightgallery/css/lightgallery.min.css","hash":"c9a2e19c932b56f4a2ce30c98910d10b74edb38a","modified":1491536483658},{"_id":"public/libs/lightgallery/css/lg-transitions.min.css","hash":"5c22e2073a4c96d6212c72135391b599e8d1359f","modified":1491536483658},{"_id":"public/libs/lightgallery/js/lg-thumbnail.js","hash":"3a6476b6df1d2bef4a21861a78776282a7a11ef1","modified":1491536483658},{"_id":"public/libs/lightgallery/js/lg-zoom.js","hash":"a758e2c8fcf710f9ff761da0eea0ab9321f3484d","modified":1491536483658},{"_id":"public/libs/lightgallery/js/lightgallery.js","hash":"3cd19b33ba99efd5ba1d167da91720566d274b2c","modified":1491536483658},{"_id":"public/libs/lightgallery/js/lightgallery.min.js","hash":"956ef9b706755318da69ad0b5d7786339d831251","modified":1491536483658},{"_id":"public/libs/jquery/2.0.3/jquery.min.js","hash":"a6eedf84389e1bc9f757bc2d19538f8c8d1cae9d","modified":1491536483658},{"_id":"public/libs/font-awesome/fonts/fontawesome-webfont.ttf","hash":"61d8d967807ef12598d81582fa95b9f600c3ee01","modified":1491536483658},{"_id":"public/libs/font-awesome/fonts/fontawesome-webfont.svg","hash":"c0522272bbaef2acb3d341912754d6ea2d0ecfc0","modified":1491536483673}],"Category":[{"name":"ios","_id":"cj17ab72p0002as5fdw0b8kpz"},{"name":"frontend","_id":"cj17ab739000kas5fxjdl2i3f"}],"Data":[],"Page":[],"Post":[{"title":"Grand Central DisPatch(GCD)概要","date":"2017-03-07T16:00:00.000Z","author":"李卓","_content":"![ ](http://o4a7cbihz.qnssl.com/cover/4c85c625-6cfe-4c6d-a40d-a04defe0705f)\n\n### CGD简要介绍\n\n有人把GCD翻译成大中枢派发(《Effective Object-C 2.0》的中文版),我觉得还是不要翻译好了。以下是来自于苹果官方文档的介绍\n\n> Grand Central Dispatch is a low-level framework in OS X that manages concurrent and asynchronous execution of tasks across the operating system. Essentially, tasks are queued and scheduled for execution as processor cores become available. By allowing the system to control the allocation of threads to tasks, GCD uses resources more effectively, which help the system and apps run faster, efficiently, and responsively.\n\n\n### 多线程介绍\n当用户启动app的时候,首先会将程序中的CPU命令序列保存在内存中。这一个完整的命令序列可以被称之为一个线程。当有多个这样的序列的时候,就被称之为多线程。\n\n虽然多线程会出现一些问题。比如多线程对临界资源的访问造成的数据不一致、不同的线程对对方拥有的资源的持续依赖而造成的死锁、使用太多的线程会消耗内存等。\n\n但是,为了保证应用程序的响应性能,我们还是会使用多线程编程。比如网络请求数据的时候,如果放在主线程去做就会影响主线程中RunLoop的执行,从而导致界面不能更新或者长时间停滞的现象,这样用户会疯的。\n\n为了解决上述问题的话,需要编写十分复杂的代码。但是GCD在做了很好的封装,一定程度上简化了解决上述问题的过程。\n\n---\n\n## GCD中一些基本API\n\n### Dispatch Queue\n\n在开发者文档中有这么一段话,我不太会翻译,自己体会吧。\n\n> You define tasks by placing the corresponding code inside either a function or a block object and adding it to a dispatch queue.\n\nDispatch Queue有两种队列,一种是Serial Dispatch Queue(串行队列,下文中就这么写了),一种是Concurrent Dispatch Queue(并行队列,下文中就这么写了)。\n\n### 两种队列的区别\n\n- 串行队列会等待现在**正在执行的任务**完成,才会处理队里中其他的任务。并行队列中**正在执行的任务**不会相互等待。\n\n- 串行队列使用**一个线程**去处理,并行队列使用**多个线程**去处理。为了避免上文中提到的问题『多线程对临界资源的访问造成的数据不一致』,需要的情况下,最好使用串行队列,因为一个线程数据更安全。\n\n\n### 队列的创建\n\n创建队列的第一种方式dispatch_create。使用生成串行队列的时候需要将dispatch_create第二个参数设置为NULL,生成并行队列的时候需要将dipatch_create的第二个参数设置为DISPATCH_QUEUE_CONCURRENT。\n\n```\ndispatch_queue_t serialQueue = dispatch_queue_create(\"com.tinfinite.ryeagleSerialQueue\", NULL);\n\ndispatch_queue_t concurentQueue = dispatch_queue_create(\"com.tinfinite.ryeagleConcurrentQueue\", DISPATCH_QUEUE_CONCURRENT);\n\n```\n\n但是还要几个问题要说,每当创建一个串行队列的时候就会新增一个线程,那么多个串行队列上的任务将会并行执行。如果创建1000个串行队列,就会有1000个线程生成。就会出现了上文中提到的多线程问题『使用太多的线程会消耗内存』。\n\n当然除了这种手动创建的方式,系统还提供了两种队列。一种就是Main Dispatch Queue,另外一种就是Global Dispatch Queue。代码如下:\n\n```\n dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();\n\n /*第一个参数决定了不同的优先级*/\n dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);\n\n```\n\n### dispatch_sync 与 dispatch_sync_async\n\ndispatch_async意味着『非同步』,也就是将指定的任务『非同步地』追加到Dispatch Queue中。dipatch_sync意味着『同步地』追加到指定的Dispatch Queue中。\n\n区别就是dispatch_async会不等待任务的完成,这个方法就会返回。而dispatch_sync则会等到任务完成了,这个方法才会返回。\n\n但是dispatch_sync使用的时候应该谨慎,因为稍加不慎,就会出现上文中多线程的第三个问题『死锁』。比如下面的两端代码:\n\n```\n dispatch_async(dispatch_get_main_queue(), ^{\n dispatch_sync(dispatch_get_main_queue(), ^{\n /*\n Task\n */\n });\n });\n\n```\n\n```\n dispatch_queue_t serialQueue = dispatch_queue_create(\"com.tinfinite.ryeagleSerialQueue\", NULL);\n\n dispatch_async(serialQueue, ^{\n dispatch_sync(serialQueue, ^{\n /*\n Task\n */\n });\n });\n\n```\n\n### dispatch_group\n\n有时候会有这样的需求,任务队列中意系列任务完成后,在做一个收尾工作。如果在串行队里中,这样做很容。但是在并行队列中这么做就不太容易了。所以就需要系统提供的dispatch_group方法了。\n\n假如现在有这么一个需求,需要异步地执行Task1、Task2、Task3。等三个任务执行完成后再执行TaskOver。使用dispatch_group的代码如下:\n\n```\n dispatch_queue_t concurentQueue = dispatch_queue_create(\"com.tinfinite.ryeagleConcurrentQueue\", DISPATCH_QUEUE_CONCURRENT);\n\n dispatch_group_t group = dispatch_group_create();\n\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 1*/\n });\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 2*/\n });\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 3*/\n });\n\n dispatch_group_notify(group, dispatch_get_main_queue(), ^{\n /*Task Over*/\n });\n\n```\n\n当然还有另外一种方式就是使用 dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout)。一般情况下使用上述方式就足够了。\n\n### dispatch_barrier_async\n\n如果现在有这样的需求,对一个数据库现有三个读取操作readTask1、readTask2、readTask3,然后再有一个写入操作writeTask,写入操作完成后再进行写入完成后的三个读取操作readTask4、readTask5、readTask6。\n\n当然如果使用串行队列,就可以避免了上文的那个『多线程对临界资源的访问造成的数据不一致』的问题,但这样效率就不高了,因为读取操作不涉及对数据的访问。\n\n为了解决这个问题,并且写出简单的代码,系统提供了一种简单的方式那就是dispatch_barrier_async方法。代码如下:\n\n```\n dispatch_async(concurentQueue, ^{\n /*Read Task 1*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 2*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 3*/\n });\n dispatch_barrier_sync(concurentQueue, ^{\n /*Write Task*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 4*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 5*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 6*/\n });\n```\n","source":"_posts/Grand Central DisPatch(GCD)概要.md","raw":"---\ntitle: Grand Central DisPatch(GCD)概要\ndate: 2017-03-08\nauthor: 李卓\ncategory: ios\ntags: GCD\n---\n![ ](http://o4a7cbihz.qnssl.com/cover/4c85c625-6cfe-4c6d-a40d-a04defe0705f)\n\n### CGD简要介绍\n\n有人把GCD翻译成大中枢派发(《Effective Object-C 2.0》的中文版),我觉得还是不要翻译好了。以下是来自于苹果官方文档的介绍\n\n> Grand Central Dispatch is a low-level framework in OS X that manages concurrent and asynchronous execution of tasks across the operating system. Essentially, tasks are queued and scheduled for execution as processor cores become available. By allowing the system to control the allocation of threads to tasks, GCD uses resources more effectively, which help the system and apps run faster, efficiently, and responsively.\n\n\n### 多线程介绍\n当用户启动app的时候,首先会将程序中的CPU命令序列保存在内存中。这一个完整的命令序列可以被称之为一个线程。当有多个这样的序列的时候,就被称之为多线程。\n\n虽然多线程会出现一些问题。比如多线程对临界资源的访问造成的数据不一致、不同的线程对对方拥有的资源的持续依赖而造成的死锁、使用太多的线程会消耗内存等。\n\n但是,为了保证应用程序的响应性能,我们还是会使用多线程编程。比如网络请求数据的时候,如果放在主线程去做就会影响主线程中RunLoop的执行,从而导致界面不能更新或者长时间停滞的现象,这样用户会疯的。\n\n为了解决上述问题的话,需要编写十分复杂的代码。但是GCD在做了很好的封装,一定程度上简化了解决上述问题的过程。\n\n---\n\n## GCD中一些基本API\n\n### Dispatch Queue\n\n在开发者文档中有这么一段话,我不太会翻译,自己体会吧。\n\n> You define tasks by placing the corresponding code inside either a function or a block object and adding it to a dispatch queue.\n\nDispatch Queue有两种队列,一种是Serial Dispatch Queue(串行队列,下文中就这么写了),一种是Concurrent Dispatch Queue(并行队列,下文中就这么写了)。\n\n### 两种队列的区别\n\n- 串行队列会等待现在**正在执行的任务**完成,才会处理队里中其他的任务。并行队列中**正在执行的任务**不会相互等待。\n\n- 串行队列使用**一个线程**去处理,并行队列使用**多个线程**去处理。为了避免上文中提到的问题『多线程对临界资源的访问造成的数据不一致』,需要的情况下,最好使用串行队列,因为一个线程数据更安全。\n\n\n### 队列的创建\n\n创建队列的第一种方式dispatch_create。使用生成串行队列的时候需要将dispatch_create第二个参数设置为NULL,生成并行队列的时候需要将dipatch_create的第二个参数设置为DISPATCH_QUEUE_CONCURRENT。\n\n```\ndispatch_queue_t serialQueue = dispatch_queue_create(\"com.tinfinite.ryeagleSerialQueue\", NULL);\n\ndispatch_queue_t concurentQueue = dispatch_queue_create(\"com.tinfinite.ryeagleConcurrentQueue\", DISPATCH_QUEUE_CONCURRENT);\n\n```\n\n但是还要几个问题要说,每当创建一个串行队列的时候就会新增一个线程,那么多个串行队列上的任务将会并行执行。如果创建1000个串行队列,就会有1000个线程生成。就会出现了上文中提到的多线程问题『使用太多的线程会消耗内存』。\n\n当然除了这种手动创建的方式,系统还提供了两种队列。一种就是Main Dispatch Queue,另外一种就是Global Dispatch Queue。代码如下:\n\n```\n dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();\n\n /*第一个参数决定了不同的优先级*/\n dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);\n\n```\n\n### dispatch_sync 与 dispatch_sync_async\n\ndispatch_async意味着『非同步』,也就是将指定的任务『非同步地』追加到Dispatch Queue中。dipatch_sync意味着『同步地』追加到指定的Dispatch Queue中。\n\n区别就是dispatch_async会不等待任务的完成,这个方法就会返回。而dispatch_sync则会等到任务完成了,这个方法才会返回。\n\n但是dispatch_sync使用的时候应该谨慎,因为稍加不慎,就会出现上文中多线程的第三个问题『死锁』。比如下面的两端代码:\n\n```\n dispatch_async(dispatch_get_main_queue(), ^{\n dispatch_sync(dispatch_get_main_queue(), ^{\n /*\n Task\n */\n });\n });\n\n```\n\n```\n dispatch_queue_t serialQueue = dispatch_queue_create(\"com.tinfinite.ryeagleSerialQueue\", NULL);\n\n dispatch_async(serialQueue, ^{\n dispatch_sync(serialQueue, ^{\n /*\n Task\n */\n });\n });\n\n```\n\n### dispatch_group\n\n有时候会有这样的需求,任务队列中意系列任务完成后,在做一个收尾工作。如果在串行队里中,这样做很容。但是在并行队列中这么做就不太容易了。所以就需要系统提供的dispatch_group方法了。\n\n假如现在有这么一个需求,需要异步地执行Task1、Task2、Task3。等三个任务执行完成后再执行TaskOver。使用dispatch_group的代码如下:\n\n```\n dispatch_queue_t concurentQueue = dispatch_queue_create(\"com.tinfinite.ryeagleConcurrentQueue\", DISPATCH_QUEUE_CONCURRENT);\n\n dispatch_group_t group = dispatch_group_create();\n\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 1*/\n });\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 2*/\n });\n dispatch_group_async(group, concurentQueue, ^{\n /*Task 3*/\n });\n\n dispatch_group_notify(group, dispatch_get_main_queue(), ^{\n /*Task Over*/\n });\n\n```\n\n当然还有另外一种方式就是使用 dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout)。一般情况下使用上述方式就足够了。\n\n### dispatch_barrier_async\n\n如果现在有这样的需求,对一个数据库现有三个读取操作readTask1、readTask2、readTask3,然后再有一个写入操作writeTask,写入操作完成后再进行写入完成后的三个读取操作readTask4、readTask5、readTask6。\n\n当然如果使用串行队列,就可以避免了上文的那个『多线程对临界资源的访问造成的数据不一致』的问题,但这样效率就不高了,因为读取操作不涉及对数据的访问。\n\n为了解决这个问题,并且写出简单的代码,系统提供了一种简单的方式那就是dispatch_barrier_async方法。代码如下:\n\n```\n dispatch_async(concurentQueue, ^{\n /*Read Task 1*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 2*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 3*/\n });\n dispatch_barrier_sync(concurentQueue, ^{\n /*Write Task*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 4*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 5*/\n });\n dispatch_async(concurentQueue, ^{\n /*Read Task 6*/\n });\n```\n","slug":"Grand Central DisPatch(GCD)概要","published":1,"updated":"2017-03-08T09:38:22.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72i0000as5frzaijj7k","content":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/4c85c625-6cfe-4c6d-a40d-a04defe0705f\" alt=\" \"></p>\n<h3 id=\"CGD简要介绍\"><a href=\"#CGD简要介绍\" class=\"headerlink\" title=\"CGD简要介绍\"></a>CGD简要介绍</h3><p>有人把GCD翻译成大中枢派发(《Effective Object-C 2.0》的中文版),我觉得还是不要翻译好了。以下是来自于苹果官方文档的介绍</p>\n<blockquote>\n<p>Grand Central Dispatch is a low-level framework in OS X that manages concurrent and asynchronous execution of tasks across the operating system. Essentially, tasks are queued and scheduled for execution as processor cores become available. By allowing the system to control the allocation of threads to tasks, GCD uses resources more effectively, which help the system and apps run faster, efficiently, and responsively.</p>\n</blockquote>\n<h3 id=\"多线程介绍\"><a href=\"#多线程介绍\" class=\"headerlink\" title=\"多线程介绍\"></a>多线程介绍</h3><p>当用户启动app的时候,首先会将程序中的CPU命令序列保存在内存中。这一个完整的命令序列可以被称之为一个线程。当有多个这样的序列的时候,就被称之为多线程。</p>\n<p>虽然多线程会出现一些问题。比如多线程对临界资源的访问造成的数据不一致、不同的线程对对方拥有的资源的持续依赖而造成的死锁、使用太多的线程会消耗内存等。</p>\n<p>但是,为了保证应用程序的响应性能,我们还是会使用多线程编程。比如网络请求数据的时候,如果放在主线程去做就会影响主线程中RunLoop的执行,从而导致界面不能更新或者长时间停滞的现象,这样用户会疯的。</p>\n<p>为了解决上述问题的话,需要编写十分复杂的代码。但是GCD在做了很好的封装,一定程度上简化了解决上述问题的过程。</p>\n<hr>\n<h2 id=\"GCD中一些基本API\"><a href=\"#GCD中一些基本API\" class=\"headerlink\" title=\"GCD中一些基本API\"></a>GCD中一些基本API</h2><h3 id=\"Dispatch-Queue\"><a href=\"#Dispatch-Queue\" class=\"headerlink\" title=\"Dispatch Queue\"></a>Dispatch Queue</h3><p>在开发者文档中有这么一段话,我不太会翻译,自己体会吧。</p>\n<blockquote>\n<p>You define tasks by placing the corresponding code inside either a function or a block object and adding it to a dispatch queue.</p>\n</blockquote>\n<p>Dispatch Queue有两种队列,一种是Serial Dispatch Queue(串行队列,下文中就这么写了),一种是Concurrent Dispatch Queue(并行队列,下文中就这么写了)。</p>\n<h3 id=\"两种队列的区别\"><a href=\"#两种队列的区别\" class=\"headerlink\" title=\"两种队列的区别\"></a>两种队列的区别</h3><ul>\n<li><p>串行队列会等待现在<strong>正在执行的任务</strong>完成,才会处理队里中其他的任务。并行队列中<strong>正在执行的任务</strong>不会相互等待。</p>\n</li>\n<li><p>串行队列使用<strong>一个线程</strong>去处理,并行队列使用<strong>多个线程</strong>去处理。为了避免上文中提到的问题『多线程对临界资源的访问造成的数据不一致』,需要的情况下,最好使用串行队列,因为一个线程数据更安全。</p>\n</li>\n</ul>\n<h3 id=\"队列的创建\"><a href=\"#队列的创建\" class=\"headerlink\" title=\"队列的创建\"></a>队列的创建</h3><p>创建队列的第一种方式dispatch_create。使用生成串行队列的时候需要将dispatch_create第二个参数设置为NULL,生成并行队列的时候需要将dipatch_create的第二个参数设置为DISPATCH_QUEUE_CONCURRENT。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t serialQueue = dispatch_queue_create("com.tinfinite.ryeagleSerialQueue", NULL);</div><div class=\"line\"></div><div class=\"line\">dispatch_queue_t concurentQueue = dispatch_queue_create("com.tinfinite.ryeagleConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);</div></pre></td></tr></table></figure>\n<p>但是还要几个问题要说,每当创建一个串行队列的时候就会新增一个线程,那么多个串行队列上的任务将会并行执行。如果创建1000个串行队列,就会有1000个线程生成。就会出现了上文中提到的多线程问题『使用太多的线程会消耗内存』。</p>\n<p>当然除了这种手动创建的方式,系统还提供了两种队列。一种就是Main Dispatch Queue,另外一种就是Global Dispatch Queue。代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();</div><div class=\"line\"></div><div class=\"line\">/*第一个参数决定了不同的优先级*/</div><div class=\"line\">dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);</div></pre></td></tr></table></figure>\n<h3 id=\"dispatch-sync-与-dispatch-sync-async\"><a href=\"#dispatch-sync-与-dispatch-sync-async\" class=\"headerlink\" title=\"dispatch_sync 与 dispatch_sync_async\"></a>dispatch_sync 与 dispatch_sync_async</h3><p>dispatch_async意味着『非同步』,也就是将指定的任务『非同步地』追加到Dispatch Queue中。dipatch_sync意味着『同步地』追加到指定的Dispatch Queue中。</p>\n<p>区别就是dispatch_async会不等待任务的完成,这个方法就会返回。而dispatch_sync则会等到任务完成了,这个方法才会返回。</p>\n<p>但是dispatch_sync使用的时候应该谨慎,因为稍加不慎,就会出现上文中多线程的第三个问题『死锁』。比如下面的两端代码:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_async(dispatch_get_main_queue(), ^{</div><div class=\"line\"> dispatch_sync(dispatch_get_main_queue(), ^{</div><div class=\"line\"> /*</div><div class=\"line\"> Task</div><div class=\"line\"> */</div><div class=\"line\"> });</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t serialQueue = dispatch_queue_create("com.tinfinite.ryeagleSerialQueue", NULL);</div><div class=\"line\"></div><div class=\"line\">dispatch_async(serialQueue, ^{</div><div class=\"line\"> dispatch_sync(serialQueue, ^{</div><div class=\"line\"> /*</div><div class=\"line\"> Task</div><div class=\"line\"> */</div><div class=\"line\"> });</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<h3 id=\"dispatch-group\"><a href=\"#dispatch-group\" class=\"headerlink\" title=\"dispatch_group\"></a>dispatch_group</h3><p>有时候会有这样的需求,任务队列中意系列任务完成后,在做一个收尾工作。如果在串行队里中,这样做很容。但是在并行队列中这么做就不太容易了。所以就需要系统提供的dispatch_group方法了。</p>\n<p>假如现在有这么一个需求,需要异步地执行Task1、Task2、Task3。等三个任务执行完成后再执行TaskOver。使用dispatch_group的代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t concurentQueue = dispatch_queue_create("com.tinfinite.ryeagleConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);</div><div class=\"line\"></div><div class=\"line\">dispatch_group_t group = dispatch_group_create();</div><div class=\"line\"></div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 1*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 2*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 3*/</div><div class=\"line\">});</div><div class=\"line\"></div><div class=\"line\">dispatch_group_notify(group, dispatch_get_main_queue(), ^{</div><div class=\"line\"> /*Task Over*/</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<p>当然还有另外一种方式就是使用 dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout)。一般情况下使用上述方式就足够了。</p>\n<h3 id=\"dispatch-barrier-async\"><a href=\"#dispatch-barrier-async\" class=\"headerlink\" title=\"dispatch_barrier_async\"></a>dispatch_barrier_async</h3><p>如果现在有这样的需求,对一个数据库现有三个读取操作readTask1、readTask2、readTask3,然后再有一个写入操作writeTask,写入操作完成后再进行写入完成后的三个读取操作readTask4、readTask5、readTask6。</p>\n<p>当然如果使用串行队列,就可以避免了上文的那个『多线程对临界资源的访问造成的数据不一致』的问题,但这样效率就不高了,因为读取操作不涉及对数据的访问。</p>\n<p>为了解决这个问题,并且写出简单的代码,系统提供了一种简单的方式那就是dispatch_barrier_async方法。代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 1*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 2*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 3*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_barrier_sync(concurentQueue, ^{</div><div class=\"line\"> /*Write Task*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 4*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 5*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 6*/</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n","excerpt":"","more":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/4c85c625-6cfe-4c6d-a40d-a04defe0705f\" alt=\" \"></p>\n<h3 id=\"CGD简要介绍\"><a href=\"#CGD简要介绍\" class=\"headerlink\" title=\"CGD简要介绍\"></a>CGD简要介绍</h3><p>有人把GCD翻译成大中枢派发(《Effective Object-C 2.0》的中文版),我觉得还是不要翻译好了。以下是来自于苹果官方文档的介绍</p>\n<blockquote>\n<p>Grand Central Dispatch is a low-level framework in OS X that manages concurrent and asynchronous execution of tasks across the operating system. Essentially, tasks are queued and scheduled for execution as processor cores become available. By allowing the system to control the allocation of threads to tasks, GCD uses resources more effectively, which help the system and apps run faster, efficiently, and responsively.</p>\n</blockquote>\n<h3 id=\"多线程介绍\"><a href=\"#多线程介绍\" class=\"headerlink\" title=\"多线程介绍\"></a>多线程介绍</h3><p>当用户启动app的时候,首先会将程序中的CPU命令序列保存在内存中。这一个完整的命令序列可以被称之为一个线程。当有多个这样的序列的时候,就被称之为多线程。</p>\n<p>虽然多线程会出现一些问题。比如多线程对临界资源的访问造成的数据不一致、不同的线程对对方拥有的资源的持续依赖而造成的死锁、使用太多的线程会消耗内存等。</p>\n<p>但是,为了保证应用程序的响应性能,我们还是会使用多线程编程。比如网络请求数据的时候,如果放在主线程去做就会影响主线程中RunLoop的执行,从而导致界面不能更新或者长时间停滞的现象,这样用户会疯的。</p>\n<p>为了解决上述问题的话,需要编写十分复杂的代码。但是GCD在做了很好的封装,一定程度上简化了解决上述问题的过程。</p>\n<hr>\n<h2 id=\"GCD中一些基本API\"><a href=\"#GCD中一些基本API\" class=\"headerlink\" title=\"GCD中一些基本API\"></a>GCD中一些基本API</h2><h3 id=\"Dispatch-Queue\"><a href=\"#Dispatch-Queue\" class=\"headerlink\" title=\"Dispatch Queue\"></a>Dispatch Queue</h3><p>在开发者文档中有这么一段话,我不太会翻译,自己体会吧。</p>\n<blockquote>\n<p>You define tasks by placing the corresponding code inside either a function or a block object and adding it to a dispatch queue.</p>\n</blockquote>\n<p>Dispatch Queue有两种队列,一种是Serial Dispatch Queue(串行队列,下文中就这么写了),一种是Concurrent Dispatch Queue(并行队列,下文中就这么写了)。</p>\n<h3 id=\"两种队列的区别\"><a href=\"#两种队列的区别\" class=\"headerlink\" title=\"两种队列的区别\"></a>两种队列的区别</h3><ul>\n<li><p>串行队列会等待现在<strong>正在执行的任务</strong>完成,才会处理队里中其他的任务。并行队列中<strong>正在执行的任务</strong>不会相互等待。</p>\n</li>\n<li><p>串行队列使用<strong>一个线程</strong>去处理,并行队列使用<strong>多个线程</strong>去处理。为了避免上文中提到的问题『多线程对临界资源的访问造成的数据不一致』,需要的情况下,最好使用串行队列,因为一个线程数据更安全。</p>\n</li>\n</ul>\n<h3 id=\"队列的创建\"><a href=\"#队列的创建\" class=\"headerlink\" title=\"队列的创建\"></a>队列的创建</h3><p>创建队列的第一种方式dispatch_create。使用生成串行队列的时候需要将dispatch_create第二个参数设置为NULL,生成并行队列的时候需要将dipatch_create的第二个参数设置为DISPATCH_QUEUE_CONCURRENT。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t serialQueue = dispatch_queue_create("com.tinfinite.ryeagleSerialQueue", NULL);</div><div class=\"line\"></div><div class=\"line\">dispatch_queue_t concurentQueue = dispatch_queue_create("com.tinfinite.ryeagleConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);</div></pre></td></tr></table></figure>\n<p>但是还要几个问题要说,每当创建一个串行队列的时候就会新增一个线程,那么多个串行队列上的任务将会并行执行。如果创建1000个串行队列,就会有1000个线程生成。就会出现了上文中提到的多线程问题『使用太多的线程会消耗内存』。</p>\n<p>当然除了这种手动创建的方式,系统还提供了两种队列。一种就是Main Dispatch Queue,另外一种就是Global Dispatch Queue。代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();</div><div class=\"line\"></div><div class=\"line\">/*第一个参数决定了不同的优先级*/</div><div class=\"line\">dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);</div></pre></td></tr></table></figure>\n<h3 id=\"dispatch-sync-与-dispatch-sync-async\"><a href=\"#dispatch-sync-与-dispatch-sync-async\" class=\"headerlink\" title=\"dispatch_sync 与 dispatch_sync_async\"></a>dispatch_sync 与 dispatch_sync_async</h3><p>dispatch_async意味着『非同步』,也就是将指定的任务『非同步地』追加到Dispatch Queue中。dipatch_sync意味着『同步地』追加到指定的Dispatch Queue中。</p>\n<p>区别就是dispatch_async会不等待任务的完成,这个方法就会返回。而dispatch_sync则会等到任务完成了,这个方法才会返回。</p>\n<p>但是dispatch_sync使用的时候应该谨慎,因为稍加不慎,就会出现上文中多线程的第三个问题『死锁』。比如下面的两端代码:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_async(dispatch_get_main_queue(), ^{</div><div class=\"line\"> dispatch_sync(dispatch_get_main_queue(), ^{</div><div class=\"line\"> /*</div><div class=\"line\"> Task</div><div class=\"line\"> */</div><div class=\"line\"> });</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t serialQueue = dispatch_queue_create("com.tinfinite.ryeagleSerialQueue", NULL);</div><div class=\"line\"></div><div class=\"line\">dispatch_async(serialQueue, ^{</div><div class=\"line\"> dispatch_sync(serialQueue, ^{</div><div class=\"line\"> /*</div><div class=\"line\"> Task</div><div class=\"line\"> */</div><div class=\"line\"> });</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<h3 id=\"dispatch-group\"><a href=\"#dispatch-group\" class=\"headerlink\" title=\"dispatch_group\"></a>dispatch_group</h3><p>有时候会有这样的需求,任务队列中意系列任务完成后,在做一个收尾工作。如果在串行队里中,这样做很容。但是在并行队列中这么做就不太容易了。所以就需要系统提供的dispatch_group方法了。</p>\n<p>假如现在有这么一个需求,需要异步地执行Task1、Task2、Task3。等三个任务执行完成后再执行TaskOver。使用dispatch_group的代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_queue_t concurentQueue = dispatch_queue_create("com.tinfinite.ryeagleConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);</div><div class=\"line\"></div><div class=\"line\">dispatch_group_t group = dispatch_group_create();</div><div class=\"line\"></div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 1*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 2*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_group_async(group, concurentQueue, ^{</div><div class=\"line\"> /*Task 3*/</div><div class=\"line\">});</div><div class=\"line\"></div><div class=\"line\">dispatch_group_notify(group, dispatch_get_main_queue(), ^{</div><div class=\"line\"> /*Task Over*/</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n<p>当然还有另外一种方式就是使用 dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout)。一般情况下使用上述方式就足够了。</p>\n<h3 id=\"dispatch-barrier-async\"><a href=\"#dispatch-barrier-async\" class=\"headerlink\" title=\"dispatch_barrier_async\"></a>dispatch_barrier_async</h3><p>如果现在有这样的需求,对一个数据库现有三个读取操作readTask1、readTask2、readTask3,然后再有一个写入操作writeTask,写入操作完成后再进行写入完成后的三个读取操作readTask4、readTask5、readTask6。</p>\n<p>当然如果使用串行队列,就可以避免了上文的那个『多线程对临界资源的访问造成的数据不一致』的问题,但这样效率就不高了,因为读取操作不涉及对数据的访问。</p>\n<p>为了解决这个问题,并且写出简单的代码,系统提供了一种简单的方式那就是dispatch_barrier_async方法。代码如下:</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div></pre></td><td class=\"code\"><pre><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 1*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 2*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 3*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_barrier_sync(concurentQueue, ^{</div><div class=\"line\"> /*Write Task*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 4*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 5*/</div><div class=\"line\">});</div><div class=\"line\">dispatch_async(concurentQueue, ^{</div><div class=\"line\"> /*Read Task 6*/</div><div class=\"line\">});</div></pre></td></tr></table></figure>\n"},{"title":"Pull Request 的正确姿势","date":"2017-03-27T16:00:00.000Z","author":"张琦","_content":"\n![Pull Request](https://upload-images.jianshu.io/upload_images/3667125-04eaaa446017c619.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n我们现在的开发流程中添加了 review 的流程,目前是依赖 Pull Request 来进行 review。不得不说没有整理过的 Pull Request 在 review 的时候相当痛苦:\n\n 1. 一个 Pull Request 中可能包含几十个 commit,内容非常多。\n 2. 可能有多个 commit 修改同一个文件,按 commit 查看时难以读懂,例如早些的 commit 做了一处错误的修改,晚些的 commit 又修复了这个问题。\n 3. 有很多无效的 commit,例如忘记修改版本号的补充,清理 log 代码的提交。\n 4. 混乱的 commit,合并的时候产生冲突,解决冲突重新提交后就变成了一个 commit,完全不知道这个 commit 中包含了哪些改动。\n\n我们应该有效的管理我们的提交,在开发期间 commit 自然越细碎越好,每个小功能点的修改做一个单独的提交对代码有助于代码合并。在合并时应该对 commit 节点进行整理,对同一个模块修改的多个 commit 应该合并成一个并写好描述,同一个分支的代码在合并时应该调整成顺序连续,方便回滚。\n\n下面是几个有用的命令:\n\n## git rebase\n关于 rebase 命令的基本用法详见:<http://gitbook.liuhui998.com/4_2.html>\nrebase 命令的主要作用是在合并的时候调整 commit 节点的顺序。\n例如我们从 develop 分支拉出了一个 featureA 分支进行开发,等开发完毕时,develop 分支已经合并了其它 feature 分支的提交。如果直接使用merge命令,合并完之后的节点按时间顺序排列,很可能是混杂在一起的,假如需要回滚代码十分的不方便。使用rebase命令合并会使 featureA 分支的所有节点在合并后仍是连续的,对回滚代码十分方便。\nrebase 还有交互模式,使用 rebase -i 可以进入交互模式。交互模式下可以灵活的对节点进行合并,在编辑界面中使用 squash 和 fixup 可以将节点合并到前一个 pick 的节点。交互模式的具体用法可以参考:<http://chuansong.me/n/447693>\n\n## git merge --squash\n如果当前开发分支 featureA 包含的内容很简单,比如修复了一个 bug,但是产生了多次提交。那么也可以在 merge 的时候添加 squash 参数,这是 rebase 的简单粗暴版,将 featureA 上产生的提交合并成一个节点。尽管效果上和 rebase 类似,但是原理并不一样,merge --squash 的时候是将 featureA 分支上的改动全部摘过来,需要自己重新 commit。","source":"_posts/Pull Request的正确姿势.md","raw":"---\ntitle: Pull Request 的正确姿势\ndate: 2017-03-28\ncategory: ios\nauthor: 张琦\n---\n\n![Pull Request](https://upload-images.jianshu.io/upload_images/3667125-04eaaa446017c619.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n我们现在的开发流程中添加了 review 的流程,目前是依赖 Pull Request 来进行 review。不得不说没有整理过的 Pull Request 在 review 的时候相当痛苦:\n\n 1. 一个 Pull Request 中可能包含几十个 commit,内容非常多。\n 2. 可能有多个 commit 修改同一个文件,按 commit 查看时难以读懂,例如早些的 commit 做了一处错误的修改,晚些的 commit 又修复了这个问题。\n 3. 有很多无效的 commit,例如忘记修改版本号的补充,清理 log 代码的提交。\n 4. 混乱的 commit,合并的时候产生冲突,解决冲突重新提交后就变成了一个 commit,完全不知道这个 commit 中包含了哪些改动。\n\n我们应该有效的管理我们的提交,在开发期间 commit 自然越细碎越好,每个小功能点的修改做一个单独的提交对代码有助于代码合并。在合并时应该对 commit 节点进行整理,对同一个模块修改的多个 commit 应该合并成一个并写好描述,同一个分支的代码在合并时应该调整成顺序连续,方便回滚。\n\n下面是几个有用的命令:\n\n## git rebase\n关于 rebase 命令的基本用法详见:<http://gitbook.liuhui998.com/4_2.html>\nrebase 命令的主要作用是在合并的时候调整 commit 节点的顺序。\n例如我们从 develop 分支拉出了一个 featureA 分支进行开发,等开发完毕时,develop 分支已经合并了其它 feature 分支的提交。如果直接使用merge命令,合并完之后的节点按时间顺序排列,很可能是混杂在一起的,假如需要回滚代码十分的不方便。使用rebase命令合并会使 featureA 分支的所有节点在合并后仍是连续的,对回滚代码十分方便。\nrebase 还有交互模式,使用 rebase -i 可以进入交互模式。交互模式下可以灵活的对节点进行合并,在编辑界面中使用 squash 和 fixup 可以将节点合并到前一个 pick 的节点。交互模式的具体用法可以参考:<http://chuansong.me/n/447693>\n\n## git merge --squash\n如果当前开发分支 featureA 包含的内容很简单,比如修复了一个 bug,但是产生了多次提交。那么也可以在 merge 的时候添加 squash 参数,这是 rebase 的简单粗暴版,将 featureA 上产生的提交合并成一个节点。尽管效果上和 rebase 类似,但是原理并不一样,merge --squash 的时候是将 featureA 分支上的改动全部摘过来,需要自己重新 commit。","slug":"Pull Request的正确姿势","published":1,"updated":"2017-03-28T01:48:27.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72n0001as5f28znqyx4","content":"<p><img src=\"https://upload-images.jianshu.io/upload_images/3667125-04eaaa446017c619.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"Pull Request\"></p>\n<p>我们现在的开发流程中添加了 review 的流程,目前是依赖 Pull Request 来进行 review。不得不说没有整理过的 Pull Request 在 review 的时候相当痛苦:</p>\n<ol>\n<li>一个 Pull Request 中可能包含几十个 commit,内容非常多。</li>\n<li>可能有多个 commit 修改同一个文件,按 commit 查看时难以读懂,例如早些的 commit 做了一处错误的修改,晚些的 commit 又修复了这个问题。</li>\n<li>有很多无效的 commit,例如忘记修改版本号的补充,清理 log 代码的提交。</li>\n<li>混乱的 commit,合并的时候产生冲突,解决冲突重新提交后就变成了一个 commit,完全不知道这个 commit 中包含了哪些改动。</li>\n</ol>\n<p>我们应该有效的管理我们的提交,在开发期间 commit 自然越细碎越好,每个小功能点的修改做一个单独的提交对代码有助于代码合并。在合并时应该对 commit 节点进行整理,对同一个模块修改的多个 commit 应该合并成一个并写好描述,同一个分支的代码在合并时应该调整成顺序连续,方便回滚。</p>\n<p>下面是几个有用的命令:</p>\n<h2 id=\"git-rebase\"><a href=\"#git-rebase\" class=\"headerlink\" title=\"git rebase\"></a>git rebase</h2><p>关于 rebase 命令的基本用法详见:<a href=\"http://gitbook.liuhui998.com/4_2.html\" target=\"_blank\" rel=\"external\">http://gitbook.liuhui998.com/4_2.html</a><br>rebase 命令的主要作用是在合并的时候调整 commit 节点的顺序。<br>例如我们从 develop 分支拉出了一个 featureA 分支进行开发,等开发完毕时,develop 分支已经合并了其它 feature 分支的提交。如果直接使用merge命令,合并完之后的节点按时间顺序排列,很可能是混杂在一起的,假如需要回滚代码十分的不方便。使用rebase命令合并会使 featureA 分支的所有节点在合并后仍是连续的,对回滚代码十分方便。<br>rebase 还有交互模式,使用 rebase -i 可以进入交互模式。交互模式下可以灵活的对节点进行合并,在编辑界面中使用 squash 和 fixup 可以将节点合并到前一个 pick 的节点。交互模式的具体用法可以参考:<a href=\"http://chuansong.me/n/447693\" target=\"_blank\" rel=\"external\">http://chuansong.me/n/447693</a></p>\n<h2 id=\"git-merge-–squash\"><a href=\"#git-merge-–squash\" class=\"headerlink\" title=\"git merge –squash\"></a>git merge –squash</h2><p>如果当前开发分支 featureA 包含的内容很简单,比如修复了一个 bug,但是产生了多次提交。那么也可以在 merge 的时候添加 squash 参数,这是 rebase 的简单粗暴版,将 featureA 上产生的提交合并成一个节点。尽管效果上和 rebase 类似,但是原理并不一样,merge –squash 的时候是将 featureA 分支上的改动全部摘过来,需要自己重新 commit。</p>\n","excerpt":"","more":"<p><img src=\"https://upload-images.jianshu.io/upload_images/3667125-04eaaa446017c619.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"Pull Request\"></p>\n<p>我们现在的开发流程中添加了 review 的流程,目前是依赖 Pull Request 来进行 review。不得不说没有整理过的 Pull Request 在 review 的时候相当痛苦:</p>\n<ol>\n<li>一个 Pull Request 中可能包含几十个 commit,内容非常多。</li>\n<li>可能有多个 commit 修改同一个文件,按 commit 查看时难以读懂,例如早些的 commit 做了一处错误的修改,晚些的 commit 又修复了这个问题。</li>\n<li>有很多无效的 commit,例如忘记修改版本号的补充,清理 log 代码的提交。</li>\n<li>混乱的 commit,合并的时候产生冲突,解决冲突重新提交后就变成了一个 commit,完全不知道这个 commit 中包含了哪些改动。</li>\n</ol>\n<p>我们应该有效的管理我们的提交,在开发期间 commit 自然越细碎越好,每个小功能点的修改做一个单独的提交对代码有助于代码合并。在合并时应该对 commit 节点进行整理,对同一个模块修改的多个 commit 应该合并成一个并写好描述,同一个分支的代码在合并时应该调整成顺序连续,方便回滚。</p>\n<p>下面是几个有用的命令:</p>\n<h2 id=\"git-rebase\"><a href=\"#git-rebase\" class=\"headerlink\" title=\"git rebase\"></a>git rebase</h2><p>关于 rebase 命令的基本用法详见:<a href=\"http://gitbook.liuhui998.com/4_2.html\">http://gitbook.liuhui998.com/4_2.html</a><br>rebase 命令的主要作用是在合并的时候调整 commit 节点的顺序。<br>例如我们从 develop 分支拉出了一个 featureA 分支进行开发,等开发完毕时,develop 分支已经合并了其它 feature 分支的提交。如果直接使用merge命令,合并完之后的节点按时间顺序排列,很可能是混杂在一起的,假如需要回滚代码十分的不方便。使用rebase命令合并会使 featureA 分支的所有节点在合并后仍是连续的,对回滚代码十分方便。<br>rebase 还有交互模式,使用 rebase -i 可以进入交互模式。交互模式下可以灵活的对节点进行合并,在编辑界面中使用 squash 和 fixup 可以将节点合并到前一个 pick 的节点。交互模式的具体用法可以参考:<a href=\"http://chuansong.me/n/447693\">http://chuansong.me/n/447693</a></p>\n<h2 id=\"git-merge-–squash\"><a href=\"#git-merge-–squash\" class=\"headerlink\" title=\"git merge –squash\"></a>git merge –squash</h2><p>如果当前开发分支 featureA 包含的内容很简单,比如修复了一个 bug,但是产生了多次提交。那么也可以在 merge 的时候添加 squash 参数,这是 rebase 的简单粗暴版,将 featureA 上产生的提交合并成一个节点。尽管效果上和 rebase 类似,但是原理并不一样,merge –squash 的时候是将 featureA 分支上的改动全部摘过来,需要自己重新 commit。</p>\n"},{"title":"UIImageView 是如何显示出来的","date":"2017-02-20T16:00:00.000Z","author":"张永彬","_content":"\n![Pixels Software Stack](http://o4a7cbihz.qnssl.com/cover/e220d513-25ef-4c3e-9a55-1a6c9e01ff8d \"pixels software stack\")\n\n### 概述\n\n- UIImageView是用来在视图中显示一张或多张图片的UIKit控件\n- UIImageView显示的最大的图片的尺寸为4096 x 4096(px),某些老机型为2048x2048(px) 参见:[iOSRes](http://iosres.com)\n- UIImageView的图片存储在其layer的contents变量中\n\n### 显示一张图片的代码一般写法\n1. 生成UIImageView\n2. 配置UIImageView的显示方式\n3. 生成UIImage,并赋值给UIImageView\n4. 将UIImageView添加到父视图\n\n```\nUIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];\nimageView.contentMode = UIViewContentModeScaleAspectFill;\nimageView.layer.masksToBounds = YES;\nimageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@\"test\" ofType:@\"jpg\"]];\n[self.view addSubview:imageView];\n```\n\n### 视图显示过程\n\n#### 必不可少的RunLoop\n\n`视图的显示是需要RunLoop去触发的。`在iOS中,视图的显示、事件的触发、NSTimer等都离不开RunLoop,RunLoop是一个处理事件的循环。\n\n- 一旦系统有任何事件需要发送给应用,就会发送一个mach_msg消息给当前的应用,RunLoop负责接收该消息,并转换为特定的事件进行处理。例如:用户触摸屏幕事件。\n- 应用中有些事件并不是按照代码中写的那样直接执行的,而是需要等待RunLoop,在RunLoop开始或结束的时候统一执行所有相关的事件。例如:添加视图、performSelector。\n\n总的来说,RunLoop做了两个层面的事情:\n- 封装系统消息,将应用与系统进行隔离,系统以消息的方式将应用需要的事件发送出来。\n- 收集应用本身产生的一些事件,等到特定时机再执行。\n\n#### 视图显示的软件堆栈\n\n![Pixels Software Stack](http://o4a7cbihz.qnssl.com/cover/a5bcec7f-09f8-4085-8d5f-ae424cc141df \"pixels software stack\")\n\n从上图可以看出,iOS中若想要显示图像必须要经过GPU的处理,而在软件层面GPU是由OpenGL操作的。Core Animation中的CALayer作为UIView的视图显示图层,直接与OpenGL相对应。由此可知,UIView中的视觉元素都是需要处理为OpenGL相关的代码的,这也为我们进行视觉相关的优化给出了指导原则。\n\n```\nCore Graphics是一个底层的设备无关的二维图像生成框架,又称Quartz2D。\nCore Graphics是CPU端的,若应用中存在大量的Core Graphics代码将会对CPU产生较大的压力,从而造成应用卡顿。\n合理的使用Core Graphics在CPU使用频率比较低的时候进行一些操作,主动生成GPU需要的数据,也可以减轻GPU的负担,从而提升应用整理的流畅度。\nCore Graphics是线程安全的,因此在后台线程是可以进行一些Core Graphics操作的。\n```\n\n### Core Animation显示过程\n\n#### Core Animation管线\n\n参考:[Advanced Graphics and Animations for iOS Apps](https://developer.apple.com/videos/play/wwdc2014/419/)\n\n![Core Animation Pipline](http://o4a7cbihz.qnssl.com/cover/1cd5e690-179b-4d57-9cd8-aeb332f2a0d8 \"Core Animation Pipline\")\n\n上图展示了Core Animation管线流程。\n\n首先需要知道Core Animation是分为应用端和服务端两部分的。我们在应用中可以使用的部分为应用端,准备好CALayer图层树,并将图层树压缩发给服务端的Core Animation。服务端收到发送来的图层树后先解压,然后转换为OpenGL或Metal相关的代码(Shader),OpenGL或Metal将Shader代码发送给GPU执行绘制命令,绘制的结果最后保存到了帧缓存区(FrameBuffer)中,最后屏幕在固定的时间间隔后将帧缓冲区中的数据显示到屏幕上。\n\n#### Core Animation应用端的流程\n\n在整个Core Animation管线过程中,我们唯一能控制的过程是应用端的过程,因此着重介绍下应用端的流程,即Commit Transation过程。\n\n![Commit Transation](http://o4a7cbihz.qnssl.com/cover/f49c8068-35a7-4251-a74d-4da08ec9de1e \"Commit Transation\")\n\n`在调用addSubview:方法后,会将该CALayer标记为dirty,待下次RunLoop开始后会将所有标记为dirty的CALayer放入Core Animation Pipline中进行处理。`\n\n1. Layout\n\n ![Commit Transation Layout](http://o4a7cbihz.qnssl.com/cover/d68c82e7-06bf-4275-a448-f5f37bd44d63 \"Commit Transation Layout\")\n\n 在这个过程中,将会调用UIView的layoutSubviews方法。Core Animation将会把新增的图层添加到图层树中;根据情况给CALayer的contents分配空间;一些轻量的数据查询,例如获取UILabel的text字符串等。\n\n2. Display\n\n ![Commit Transation Display](http://o4a7cbihz.qnssl.com/cover/d1de7418-7c9d-4d2c-a29c-7ddf70d947a8 \"Commit Transation Display\")\n\n 若UIView实现了drawRect:方法,此时就会调用drawRect:方法。在drawRect:方法中,一般为Core Graphics方法。在执行drawRect:方式前,应用会提前申请一个与当前视图大小相对应的内存空间(width * height * 4 * contentsScale byte)。因为Core Graphics在CPU中执行,因此需要确保Core Graphics中的代码比较简单,否则将会大量消耗CPU。\n\n3. Prepare\n\n ![Commit Transation Prepare](http://o4a7cbihz.qnssl.com/cover/d83d3eac-70b3-4446-81dc-7f8cc89f60eb \"Commit Transation Prepare\")\n\n 如果图层树中包含了图片,并且图片并未解压,那么在这时将会对图片进行解压。(直接包含在iOS工程中的PNG图片在打包的过程中将会被优化,从而在解压的过程中有更快的速度。因此建议把应用中频繁使用的图片打包到应用中。)\n\n 因为苹果设备中继承了JPEG的硬编码和硬解码,并且JPEG相对PNG所占内存比较小,因此苹果在JPEG的编解码上有一定的优势。\n\n4. Commit\n\n ![Commit Transation Commit](http://o4a7cbihz.qnssl.com/cover/6315328b-2128-482b-b7dc-b7adc337ecea \"Commit Transation Commit\")\n\n 打包图层数据,并将图层数据发送到Core Animation的服务端。若图层树中包含了大量的图层,将会延长打包的过程。\n\n 另,由于CPU与GPU之间的带宽是有限的,若图层树中包含的图片或文本数据比较多,最终发送给GPU的纹理数量会很多,也可能会引起界面的卡顿。\n\n### UIImageView是如何显示出来的\n 1. UIImageView被添加到superView上后,会将自己标记为dirty,等待下次RunLoop循环。\n 2. 执行UIImageView的layoutSubviews方法。UIImageView为layer的content变量分配空间。\n 可以自定义layoutSubviews方法,从而为UIImageView添加遮罩或其他特效(通过shadowPath添加阴影等)。\n 3. 执行UIImageView的drawRect:方法。通过Core Graphics为UIImageView添加不同的视觉效果。\n 4. 解压UIImageView中UIImage变量对应的图片。\n 5. 将UIImageView图层(CALayer)进行打包发送到Core AniAnimation的服务端。\n 6. 服务端拿到UIImageView图层后先解压,然后根据图层的属性(position,bounds,contentsScale,contentsRect,contentsGravity等)生成vertex shader,然后结合UIImageView的图片,即图层对应的纹理,生成fragment shader。\n 7. OpenGL将生成的vertex shader和fragment shader放入OpenGL Pipline中执行,GPU根据OpenGL发送过来的命令进行绘制,并将绘制的结果保存在FrameBuffer中。\n 8. 显示器在下一个显示周期到来时将FrameBuffer中的数据显示到屏幕上。\n以上就是UIImageView在iOS平台上显示出来的基本过程。\n\n当然,具体的显示过程是非常复杂的,尤其是在涉及到一些特效或动画时。\n\n参考:\n- [绘制像素到屏幕上](https://objccn.io/issue-3-1/)\n- [iOS 保持界面流畅的技巧](http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/)\n- [iOS 处理图片的一些小 Tip](http://blog.ibireme.com/2015/11/02/ios_image_tips/)\n- [移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)\n- [iOS Resource](http://iosres.com )\n- [Advanced Graphics and Animations for iOS Apps](https://developer.apple.com/videos/play/wwdc2014/419/)\n- [iOS: is Core Graphics implemented on top of OpenGL?](http://stackoverflow.com/questions/7558636/ios-is-core-graphics-implemented-on-top-of-opengl)\n- [学习OpenGL](https://learnopengl.com)\n\n`文中若有不正确的地方,烦请大家指正。`\n","source":"_posts/UIImageView是如何显示出来的.md","raw":"---\ntitle: UIImageView 是如何显示出来的\ndate: 2017-02-21\nauthor: 张永彬\ncategory: ios\ntags: UIImageView\n---\n\n![Pixels Software Stack](http://o4a7cbihz.qnssl.com/cover/e220d513-25ef-4c3e-9a55-1a6c9e01ff8d \"pixels software stack\")\n\n### 概述\n\n- UIImageView是用来在视图中显示一张或多张图片的UIKit控件\n- UIImageView显示的最大的图片的尺寸为4096 x 4096(px),某些老机型为2048x2048(px) 参见:[iOSRes](http://iosres.com)\n- UIImageView的图片存储在其layer的contents变量中\n\n### 显示一张图片的代码一般写法\n1. 生成UIImageView\n2. 配置UIImageView的显示方式\n3. 生成UIImage,并赋值给UIImageView\n4. 将UIImageView添加到父视图\n\n```\nUIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];\nimageView.contentMode = UIViewContentModeScaleAspectFill;\nimageView.layer.masksToBounds = YES;\nimageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@\"test\" ofType:@\"jpg\"]];\n[self.view addSubview:imageView];\n```\n\n### 视图显示过程\n\n#### 必不可少的RunLoop\n\n`视图的显示是需要RunLoop去触发的。`在iOS中,视图的显示、事件的触发、NSTimer等都离不开RunLoop,RunLoop是一个处理事件的循环。\n\n- 一旦系统有任何事件需要发送给应用,就会发送一个mach_msg消息给当前的应用,RunLoop负责接收该消息,并转换为特定的事件进行处理。例如:用户触摸屏幕事件。\n- 应用中有些事件并不是按照代码中写的那样直接执行的,而是需要等待RunLoop,在RunLoop开始或结束的时候统一执行所有相关的事件。例如:添加视图、performSelector。\n\n总的来说,RunLoop做了两个层面的事情:\n- 封装系统消息,将应用与系统进行隔离,系统以消息的方式将应用需要的事件发送出来。\n- 收集应用本身产生的一些事件,等到特定时机再执行。\n\n#### 视图显示的软件堆栈\n\n![Pixels Software Stack](http://o4a7cbihz.qnssl.com/cover/a5bcec7f-09f8-4085-8d5f-ae424cc141df \"pixels software stack\")\n\n从上图可以看出,iOS中若想要显示图像必须要经过GPU的处理,而在软件层面GPU是由OpenGL操作的。Core Animation中的CALayer作为UIView的视图显示图层,直接与OpenGL相对应。由此可知,UIView中的视觉元素都是需要处理为OpenGL相关的代码的,这也为我们进行视觉相关的优化给出了指导原则。\n\n```\nCore Graphics是一个底层的设备无关的二维图像生成框架,又称Quartz2D。\nCore Graphics是CPU端的,若应用中存在大量的Core Graphics代码将会对CPU产生较大的压力,从而造成应用卡顿。\n合理的使用Core Graphics在CPU使用频率比较低的时候进行一些操作,主动生成GPU需要的数据,也可以减轻GPU的负担,从而提升应用整理的流畅度。\nCore Graphics是线程安全的,因此在后台线程是可以进行一些Core Graphics操作的。\n```\n\n### Core Animation显示过程\n\n#### Core Animation管线\n\n参考:[Advanced Graphics and Animations for iOS Apps](https://developer.apple.com/videos/play/wwdc2014/419/)\n\n![Core Animation Pipline](http://o4a7cbihz.qnssl.com/cover/1cd5e690-179b-4d57-9cd8-aeb332f2a0d8 \"Core Animation Pipline\")\n\n上图展示了Core Animation管线流程。\n\n首先需要知道Core Animation是分为应用端和服务端两部分的。我们在应用中可以使用的部分为应用端,准备好CALayer图层树,并将图层树压缩发给服务端的Core Animation。服务端收到发送来的图层树后先解压,然后转换为OpenGL或Metal相关的代码(Shader),OpenGL或Metal将Shader代码发送给GPU执行绘制命令,绘制的结果最后保存到了帧缓存区(FrameBuffer)中,最后屏幕在固定的时间间隔后将帧缓冲区中的数据显示到屏幕上。\n\n#### Core Animation应用端的流程\n\n在整个Core Animation管线过程中,我们唯一能控制的过程是应用端的过程,因此着重介绍下应用端的流程,即Commit Transation过程。\n\n![Commit Transation](http://o4a7cbihz.qnssl.com/cover/f49c8068-35a7-4251-a74d-4da08ec9de1e \"Commit Transation\")\n\n`在调用addSubview:方法后,会将该CALayer标记为dirty,待下次RunLoop开始后会将所有标记为dirty的CALayer放入Core Animation Pipline中进行处理。`\n\n1. Layout\n\n ![Commit Transation Layout](http://o4a7cbihz.qnssl.com/cover/d68c82e7-06bf-4275-a448-f5f37bd44d63 \"Commit Transation Layout\")\n\n 在这个过程中,将会调用UIView的layoutSubviews方法。Core Animation将会把新增的图层添加到图层树中;根据情况给CALayer的contents分配空间;一些轻量的数据查询,例如获取UILabel的text字符串等。\n\n2. Display\n\n ![Commit Transation Display](http://o4a7cbihz.qnssl.com/cover/d1de7418-7c9d-4d2c-a29c-7ddf70d947a8 \"Commit Transation Display\")\n\n 若UIView实现了drawRect:方法,此时就会调用drawRect:方法。在drawRect:方法中,一般为Core Graphics方法。在执行drawRect:方式前,应用会提前申请一个与当前视图大小相对应的内存空间(width * height * 4 * contentsScale byte)。因为Core Graphics在CPU中执行,因此需要确保Core Graphics中的代码比较简单,否则将会大量消耗CPU。\n\n3. Prepare\n\n ![Commit Transation Prepare](http://o4a7cbihz.qnssl.com/cover/d83d3eac-70b3-4446-81dc-7f8cc89f60eb \"Commit Transation Prepare\")\n\n 如果图层树中包含了图片,并且图片并未解压,那么在这时将会对图片进行解压。(直接包含在iOS工程中的PNG图片在打包的过程中将会被优化,从而在解压的过程中有更快的速度。因此建议把应用中频繁使用的图片打包到应用中。)\n\n 因为苹果设备中继承了JPEG的硬编码和硬解码,并且JPEG相对PNG所占内存比较小,因此苹果在JPEG的编解码上有一定的优势。\n\n4. Commit\n\n ![Commit Transation Commit](http://o4a7cbihz.qnssl.com/cover/6315328b-2128-482b-b7dc-b7adc337ecea \"Commit Transation Commit\")\n\n 打包图层数据,并将图层数据发送到Core Animation的服务端。若图层树中包含了大量的图层,将会延长打包的过程。\n\n 另,由于CPU与GPU之间的带宽是有限的,若图层树中包含的图片或文本数据比较多,最终发送给GPU的纹理数量会很多,也可能会引起界面的卡顿。\n\n### UIImageView是如何显示出来的\n 1. UIImageView被添加到superView上后,会将自己标记为dirty,等待下次RunLoop循环。\n 2. 执行UIImageView的layoutSubviews方法。UIImageView为layer的content变量分配空间。\n 可以自定义layoutSubviews方法,从而为UIImageView添加遮罩或其他特效(通过shadowPath添加阴影等)。\n 3. 执行UIImageView的drawRect:方法。通过Core Graphics为UIImageView添加不同的视觉效果。\n 4. 解压UIImageView中UIImage变量对应的图片。\n 5. 将UIImageView图层(CALayer)进行打包发送到Core AniAnimation的服务端。\n 6. 服务端拿到UIImageView图层后先解压,然后根据图层的属性(position,bounds,contentsScale,contentsRect,contentsGravity等)生成vertex shader,然后结合UIImageView的图片,即图层对应的纹理,生成fragment shader。\n 7. OpenGL将生成的vertex shader和fragment shader放入OpenGL Pipline中执行,GPU根据OpenGL发送过来的命令进行绘制,并将绘制的结果保存在FrameBuffer中。\n 8. 显示器在下一个显示周期到来时将FrameBuffer中的数据显示到屏幕上。\n以上就是UIImageView在iOS平台上显示出来的基本过程。\n\n当然,具体的显示过程是非常复杂的,尤其是在涉及到一些特效或动画时。\n\n参考:\n- [绘制像素到屏幕上](https://objccn.io/issue-3-1/)\n- [iOS 保持界面流畅的技巧](http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/)\n- [iOS 处理图片的一些小 Tip](http://blog.ibireme.com/2015/11/02/ios_image_tips/)\n- [移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)\n- [iOS Resource](http://iosres.com )\n- [Advanced Graphics and Animations for iOS Apps](https://developer.apple.com/videos/play/wwdc2014/419/)\n- [iOS: is Core Graphics implemented on top of OpenGL?](http://stackoverflow.com/questions/7558636/ios-is-core-graphics-implemented-on-top-of-opengl)\n- [学习OpenGL](https://learnopengl.com)\n\n`文中若有不正确的地方,烦请大家指正。`\n","slug":"UIImageView是如何显示出来的","published":1,"updated":"2017-02-21T13:55:22.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72s0004as5fo1la6bgu","content":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/e220d513-25ef-4c3e-9a55-1a6c9e01ff8d\" alt=\"Pixels Software Stack\" title=\"pixels software stack\"></p>\n<h3 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h3><ul>\n<li>UIImageView是用来在视图中显示一张或多张图片的UIKit控件</li>\n<li>UIImageView显示的最大的图片的尺寸为4096 x 4096(px),某些老机型为2048x2048(px) 参见:<a href=\"http://iosres.com\" target=\"_blank\" rel=\"external\">iOSRes</a></li>\n<li>UIImageView的图片存储在其layer的contents变量中</li>\n</ul>\n<h3 id=\"显示一张图片的代码一般写法\"><a href=\"#显示一张图片的代码一般写法\" class=\"headerlink\" title=\"显示一张图片的代码一般写法\"></a>显示一张图片的代码一般写法</h3><ol>\n<li>生成UIImageView</li>\n<li>配置UIImageView的显示方式</li>\n<li>生成UIImage,并赋值给UIImageView</li>\n<li>将UIImageView添加到父视图</li>\n</ol>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div></pre></td><td class=\"code\"><pre><div class=\"line\">UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];</div><div class=\"line\">imageView.contentMode = UIViewContentModeScaleAspectFill;</div><div class=\"line\">imageView.layer.masksToBounds = YES;</div><div class=\"line\">imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"]];</div><div class=\"line\">[self.view addSubview:imageView];</div></pre></td></tr></table></figure>\n<h3 id=\"视图显示过程\"><a href=\"#视图显示过程\" class=\"headerlink\" title=\"视图显示过程\"></a>视图显示过程</h3><h4 id=\"必不可少的RunLoop\"><a href=\"#必不可少的RunLoop\" class=\"headerlink\" title=\"必不可少的RunLoop\"></a>必不可少的RunLoop</h4><p><code>视图的显示是需要RunLoop去触发的。</code>在iOS中,视图的显示、事件的触发、NSTimer等都离不开RunLoop,RunLoop是一个处理事件的循环。</p>\n<ul>\n<li>一旦系统有任何事件需要发送给应用,就会发送一个mach_msg消息给当前的应用,RunLoop负责接收该消息,并转换为特定的事件进行处理。例如:用户触摸屏幕事件。</li>\n<li>应用中有些事件并不是按照代码中写的那样直接执行的,而是需要等待RunLoop,在RunLoop开始或结束的时候统一执行所有相关的事件。例如:添加视图、performSelector。</li>\n</ul>\n<p>总的来说,RunLoop做了两个层面的事情:</p>\n<ul>\n<li>封装系统消息,将应用与系统进行隔离,系统以消息的方式将应用需要的事件发送出来。</li>\n<li>收集应用本身产生的一些事件,等到特定时机再执行。</li>\n</ul>\n<h4 id=\"视图显示的软件堆栈\"><a href=\"#视图显示的软件堆栈\" class=\"headerlink\" title=\"视图显示的软件堆栈\"></a>视图显示的软件堆栈</h4><p><img src=\"http://o4a7cbihz.qnssl.com/cover/a5bcec7f-09f8-4085-8d5f-ae424cc141df\" alt=\"Pixels Software Stack\" title=\"pixels software stack\"></p>\n<p>从上图可以看出,iOS中若想要显示图像必须要经过GPU的处理,而在软件层面GPU是由OpenGL操作的。Core Animation中的CALayer作为UIView的视图显示图层,直接与OpenGL相对应。由此可知,UIView中的视觉元素都是需要处理为OpenGL相关的代码的,这也为我们进行视觉相关的优化给出了指导原则。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">Core Graphics是一个底层的设备无关的二维图像生成框架,又称Quartz2D。</div><div class=\"line\">Core Graphics是CPU端的,若应用中存在大量的Core Graphics代码将会对CPU产生较大的压力,从而造成应用卡顿。</div><div class=\"line\">合理的使用Core Graphics在CPU使用频率比较低的时候进行一些操作,主动生成GPU需要的数据,也可以减轻GPU的负担,从而提升应用整理的流畅度。</div><div class=\"line\">Core Graphics是线程安全的,因此在后台线程是可以进行一些Core Graphics操作的。</div></pre></td></tr></table></figure>\n<h3 id=\"Core-Animation显示过程\"><a href=\"#Core-Animation显示过程\" class=\"headerlink\" title=\"Core Animation显示过程\"></a>Core Animation显示过程</h3><h4 id=\"Core-Animation管线\"><a href=\"#Core-Animation管线\" class=\"headerlink\" title=\"Core Animation管线\"></a>Core Animation管线</h4><p>参考:<a href=\"https://developer.apple.com/videos/play/wwdc2014/419/\" target=\"_blank\" rel=\"external\">Advanced Graphics and Animations for iOS Apps</a></p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/1cd5e690-179b-4d57-9cd8-aeb332f2a0d8\" alt=\"Core Animation Pipline\" title=\"Core Animation Pipline\"></p>\n<p>上图展示了Core Animation管线流程。</p>\n<p>首先需要知道Core Animation是分为应用端和服务端两部分的。我们在应用中可以使用的部分为应用端,准备好CALayer图层树,并将图层树压缩发给服务端的Core Animation。服务端收到发送来的图层树后先解压,然后转换为OpenGL或Metal相关的代码(Shader),OpenGL或Metal将Shader代码发送给GPU执行绘制命令,绘制的结果最后保存到了帧缓存区(FrameBuffer)中,最后屏幕在固定的时间间隔后将帧缓冲区中的数据显示到屏幕上。</p>\n<h4 id=\"Core-Animation应用端的流程\"><a href=\"#Core-Animation应用端的流程\" class=\"headerlink\" title=\"Core Animation应用端的流程\"></a>Core Animation应用端的流程</h4><p>在整个Core Animation管线过程中,我们唯一能控制的过程是应用端的过程,因此着重介绍下应用端的流程,即Commit Transation过程。</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/f49c8068-35a7-4251-a74d-4da08ec9de1e\" alt=\"Commit Transation\" title=\"Commit Transation\"></p>\n<p><code>在调用addSubview:方法后,会将该CALayer标记为dirty,待下次RunLoop开始后会将所有标记为dirty的CALayer放入Core Animation Pipline中进行处理。</code></p>\n<ol>\n<li><p>Layout</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d68c82e7-06bf-4275-a448-f5f37bd44d63\" alt=\"Commit Transation Layout\" title=\"Commit Transation Layout\"></p>\n<p>在这个过程中,将会调用UIView的layoutSubviews方法。Core Animation将会把新增的图层添加到图层树中;根据情况给CALayer的contents分配空间;一些轻量的数据查询,例如获取UILabel的text字符串等。</p>\n</li>\n<li><p>Display</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d1de7418-7c9d-4d2c-a29c-7ddf70d947a8\" alt=\"Commit Transation Display\" title=\"Commit Transation Display\"></p>\n<p>若UIView实现了drawRect:方法,此时就会调用drawRect:方法。在drawRect:方法中,一般为Core Graphics方法。在执行drawRect:方式前,应用会提前申请一个与当前视图大小相对应的内存空间(width <em> height </em> 4 * contentsScale byte)。因为Core Graphics在CPU中执行,因此需要确保Core Graphics中的代码比较简单,否则将会大量消耗CPU。</p>\n</li>\n<li><p>Prepare</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d83d3eac-70b3-4446-81dc-7f8cc89f60eb\" alt=\"Commit Transation Prepare\" title=\"Commit Transation Prepare\"></p>\n<p>如果图层树中包含了图片,并且图片并未解压,那么在这时将会对图片进行解压。(直接包含在iOS工程中的PNG图片在打包的过程中将会被优化,从而在解压的过程中有更快的速度。因此建议把应用中频繁使用的图片打包到应用中。)</p>\n<p>因为苹果设备中继承了JPEG的硬编码和硬解码,并且JPEG相对PNG所占内存比较小,因此苹果在JPEG的编解码上有一定的优势。</p>\n</li>\n<li><p>Commit</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/6315328b-2128-482b-b7dc-b7adc337ecea\" alt=\"Commit Transation Commit\" title=\"Commit Transation Commit\"></p>\n<p>打包图层数据,并将图层数据发送到Core Animation的服务端。若图层树中包含了大量的图层,将会延长打包的过程。</p>\n<p>另,由于CPU与GPU之间的带宽是有限的,若图层树中包含的图片或文本数据比较多,最终发送给GPU的纹理数量会很多,也可能会引起界面的卡顿。</p>\n</li>\n</ol>\n<h3 id=\"UIImageView是如何显示出来的\"><a href=\"#UIImageView是如何显示出来的\" class=\"headerlink\" title=\"UIImageView是如何显示出来的\"></a>UIImageView是如何显示出来的</h3><ol>\n<li>UIImageView被添加到superView上后,会将自己标记为dirty,等待下次RunLoop循环。</li>\n<li>执行UIImageView的layoutSubviews方法。UIImageView为layer的content变量分配空间。<br>可以自定义layoutSubviews方法,从而为UIImageView添加遮罩或其他特效(通过shadowPath添加阴影等)。</li>\n<li>执行UIImageView的drawRect:方法。通过Core Graphics为UIImageView添加不同的视觉效果。</li>\n<li>解压UIImageView中UIImage变量对应的图片。</li>\n<li>将UIImageView图层(CALayer)进行打包发送到Core AniAnimation的服务端。</li>\n<li>服务端拿到UIImageView图层后先解压,然后根据图层的属性(position,bounds,contentsScale,contentsRect,contentsGravity等)生成vertex shader,然后结合UIImageView的图片,即图层对应的纹理,生成fragment shader。</li>\n<li>OpenGL将生成的vertex shader和fragment shader放入OpenGL Pipline中执行,GPU根据OpenGL发送过来的命令进行绘制,并将绘制的结果保存在FrameBuffer中。</li>\n<li>显示器在下一个显示周期到来时将FrameBuffer中的数据显示到屏幕上。<br>以上就是UIImageView在iOS平台上显示出来的基本过程。</li>\n</ol>\n<p>当然,具体的显示过程是非常复杂的,尤其是在涉及到一些特效或动画时。</p>\n<p>参考:</p>\n<ul>\n<li><a href=\"https://objccn.io/issue-3-1/\" target=\"_blank\" rel=\"external\">绘制像素到屏幕上</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/\" target=\"_blank\" rel=\"external\">iOS 保持界面流畅的技巧</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/02/ios_image_tips/\" target=\"_blank\" rel=\"external\">iOS 处理图片的一些小 Tip</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/\" target=\"_blank\" rel=\"external\">移动端图片格式调研</a></li>\n<li><a href=\"http://iosres.com\" target=\"_blank\" rel=\"external\">iOS Resource</a></li>\n<li><a href=\"https://developer.apple.com/videos/play/wwdc2014/419/\" target=\"_blank\" rel=\"external\">Advanced Graphics and Animations for iOS Apps</a></li>\n<li><a href=\"http://stackoverflow.com/questions/7558636/ios-is-core-graphics-implemented-on-top-of-opengl\" target=\"_blank\" rel=\"external\">iOS: is Core Graphics implemented on top of OpenGL?</a></li>\n<li><a href=\"https://learnopengl.com\" target=\"_blank\" rel=\"external\">学习OpenGL</a></li>\n</ul>\n<p><code>文中若有不正确的地方,烦请大家指正。</code></p>\n","excerpt":"","more":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/e220d513-25ef-4c3e-9a55-1a6c9e01ff8d\" alt=\"Pixels Software Stack\" title=\"pixels software stack\"></p>\n<h3 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h3><ul>\n<li>UIImageView是用来在视图中显示一张或多张图片的UIKit控件</li>\n<li>UIImageView显示的最大的图片的尺寸为4096 x 4096(px),某些老机型为2048x2048(px) 参见:<a href=\"http://iosres.com\">iOSRes</a></li>\n<li>UIImageView的图片存储在其layer的contents变量中</li>\n</ul>\n<h3 id=\"显示一张图片的代码一般写法\"><a href=\"#显示一张图片的代码一般写法\" class=\"headerlink\" title=\"显示一张图片的代码一般写法\"></a>显示一张图片的代码一般写法</h3><ol>\n<li>生成UIImageView</li>\n<li>配置UIImageView的显示方式</li>\n<li>生成UIImage,并赋值给UIImageView</li>\n<li>将UIImageView添加到父视图</li>\n</ol>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div></pre></td><td class=\"code\"><pre><div class=\"line\">UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];</div><div class=\"line\">imageView.contentMode = UIViewContentModeScaleAspectFill;</div><div class=\"line\">imageView.layer.masksToBounds = YES;</div><div class=\"line\">imageView.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"]];</div><div class=\"line\">[self.view addSubview:imageView];</div></pre></td></tr></table></figure>\n<h3 id=\"视图显示过程\"><a href=\"#视图显示过程\" class=\"headerlink\" title=\"视图显示过程\"></a>视图显示过程</h3><h4 id=\"必不可少的RunLoop\"><a href=\"#必不可少的RunLoop\" class=\"headerlink\" title=\"必不可少的RunLoop\"></a>必不可少的RunLoop</h4><p><code>视图的显示是需要RunLoop去触发的。</code>在iOS中,视图的显示、事件的触发、NSTimer等都离不开RunLoop,RunLoop是一个处理事件的循环。</p>\n<ul>\n<li>一旦系统有任何事件需要发送给应用,就会发送一个mach_msg消息给当前的应用,RunLoop负责接收该消息,并转换为特定的事件进行处理。例如:用户触摸屏幕事件。</li>\n<li>应用中有些事件并不是按照代码中写的那样直接执行的,而是需要等待RunLoop,在RunLoop开始或结束的时候统一执行所有相关的事件。例如:添加视图、performSelector。</li>\n</ul>\n<p>总的来说,RunLoop做了两个层面的事情:</p>\n<ul>\n<li>封装系统消息,将应用与系统进行隔离,系统以消息的方式将应用需要的事件发送出来。</li>\n<li>收集应用本身产生的一些事件,等到特定时机再执行。</li>\n</ul>\n<h4 id=\"视图显示的软件堆栈\"><a href=\"#视图显示的软件堆栈\" class=\"headerlink\" title=\"视图显示的软件堆栈\"></a>视图显示的软件堆栈</h4><p><img src=\"http://o4a7cbihz.qnssl.com/cover/a5bcec7f-09f8-4085-8d5f-ae424cc141df\" alt=\"Pixels Software Stack\" title=\"pixels software stack\"></p>\n<p>从上图可以看出,iOS中若想要显示图像必须要经过GPU的处理,而在软件层面GPU是由OpenGL操作的。Core Animation中的CALayer作为UIView的视图显示图层,直接与OpenGL相对应。由此可知,UIView中的视觉元素都是需要处理为OpenGL相关的代码的,这也为我们进行视觉相关的优化给出了指导原则。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">Core Graphics是一个底层的设备无关的二维图像生成框架,又称Quartz2D。</div><div class=\"line\">Core Graphics是CPU端的,若应用中存在大量的Core Graphics代码将会对CPU产生较大的压力,从而造成应用卡顿。</div><div class=\"line\">合理的使用Core Graphics在CPU使用频率比较低的时候进行一些操作,主动生成GPU需要的数据,也可以减轻GPU的负担,从而提升应用整理的流畅度。</div><div class=\"line\">Core Graphics是线程安全的,因此在后台线程是可以进行一些Core Graphics操作的。</div></pre></td></tr></table></figure>\n<h3 id=\"Core-Animation显示过程\"><a href=\"#Core-Animation显示过程\" class=\"headerlink\" title=\"Core Animation显示过程\"></a>Core Animation显示过程</h3><h4 id=\"Core-Animation管线\"><a href=\"#Core-Animation管线\" class=\"headerlink\" title=\"Core Animation管线\"></a>Core Animation管线</h4><p>参考:<a href=\"https://developer.apple.com/videos/play/wwdc2014/419/\">Advanced Graphics and Animations for iOS Apps</a></p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/1cd5e690-179b-4d57-9cd8-aeb332f2a0d8\" alt=\"Core Animation Pipline\" title=\"Core Animation Pipline\"></p>\n<p>上图展示了Core Animation管线流程。</p>\n<p>首先需要知道Core Animation是分为应用端和服务端两部分的。我们在应用中可以使用的部分为应用端,准备好CALayer图层树,并将图层树压缩发给服务端的Core Animation。服务端收到发送来的图层树后先解压,然后转换为OpenGL或Metal相关的代码(Shader),OpenGL或Metal将Shader代码发送给GPU执行绘制命令,绘制的结果最后保存到了帧缓存区(FrameBuffer)中,最后屏幕在固定的时间间隔后将帧缓冲区中的数据显示到屏幕上。</p>\n<h4 id=\"Core-Animation应用端的流程\"><a href=\"#Core-Animation应用端的流程\" class=\"headerlink\" title=\"Core Animation应用端的流程\"></a>Core Animation应用端的流程</h4><p>在整个Core Animation管线过程中,我们唯一能控制的过程是应用端的过程,因此着重介绍下应用端的流程,即Commit Transation过程。</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/f49c8068-35a7-4251-a74d-4da08ec9de1e\" alt=\"Commit Transation\" title=\"Commit Transation\"></p>\n<p><code>在调用addSubview:方法后,会将该CALayer标记为dirty,待下次RunLoop开始后会将所有标记为dirty的CALayer放入Core Animation Pipline中进行处理。</code></p>\n<ol>\n<li><p>Layout</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d68c82e7-06bf-4275-a448-f5f37bd44d63\" alt=\"Commit Transation Layout\" title=\"Commit Transation Layout\"></p>\n<p>在这个过程中,将会调用UIView的layoutSubviews方法。Core Animation将会把新增的图层添加到图层树中;根据情况给CALayer的contents分配空间;一些轻量的数据查询,例如获取UILabel的text字符串等。</p>\n</li>\n<li><p>Display</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d1de7418-7c9d-4d2c-a29c-7ddf70d947a8\" alt=\"Commit Transation Display\" title=\"Commit Transation Display\"></p>\n<p>若UIView实现了drawRect:方法,此时就会调用drawRect:方法。在drawRect:方法中,一般为Core Graphics方法。在执行drawRect:方式前,应用会提前申请一个与当前视图大小相对应的内存空间(width <em> height </em> 4 * contentsScale byte)。因为Core Graphics在CPU中执行,因此需要确保Core Graphics中的代码比较简单,否则将会大量消耗CPU。</p>\n</li>\n<li><p>Prepare</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/d83d3eac-70b3-4446-81dc-7f8cc89f60eb\" alt=\"Commit Transation Prepare\" title=\"Commit Transation Prepare\"></p>\n<p>如果图层树中包含了图片,并且图片并未解压,那么在这时将会对图片进行解压。(直接包含在iOS工程中的PNG图片在打包的过程中将会被优化,从而在解压的过程中有更快的速度。因此建议把应用中频繁使用的图片打包到应用中。)</p>\n<p>因为苹果设备中继承了JPEG的硬编码和硬解码,并且JPEG相对PNG所占内存比较小,因此苹果在JPEG的编解码上有一定的优势。</p>\n</li>\n<li><p>Commit</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/6315328b-2128-482b-b7dc-b7adc337ecea\" alt=\"Commit Transation Commit\" title=\"Commit Transation Commit\"></p>\n<p>打包图层数据,并将图层数据发送到Core Animation的服务端。若图层树中包含了大量的图层,将会延长打包的过程。</p>\n<p>另,由于CPU与GPU之间的带宽是有限的,若图层树中包含的图片或文本数据比较多,最终发送给GPU的纹理数量会很多,也可能会引起界面的卡顿。</p>\n</li>\n</ol>\n<h3 id=\"UIImageView是如何显示出来的\"><a href=\"#UIImageView是如何显示出来的\" class=\"headerlink\" title=\"UIImageView是如何显示出来的\"></a>UIImageView是如何显示出来的</h3><ol>\n<li>UIImageView被添加到superView上后,会将自己标记为dirty,等待下次RunLoop循环。</li>\n<li>执行UIImageView的layoutSubviews方法。UIImageView为layer的content变量分配空间。<br>可以自定义layoutSubviews方法,从而为UIImageView添加遮罩或其他特效(通过shadowPath添加阴影等)。</li>\n<li>执行UIImageView的drawRect:方法。通过Core Graphics为UIImageView添加不同的视觉效果。</li>\n<li>解压UIImageView中UIImage变量对应的图片。</li>\n<li>将UIImageView图层(CALayer)进行打包发送到Core AniAnimation的服务端。</li>\n<li>服务端拿到UIImageView图层后先解压,然后根据图层的属性(position,bounds,contentsScale,contentsRect,contentsGravity等)生成vertex shader,然后结合UIImageView的图片,即图层对应的纹理,生成fragment shader。</li>\n<li>OpenGL将生成的vertex shader和fragment shader放入OpenGL Pipline中执行,GPU根据OpenGL发送过来的命令进行绘制,并将绘制的结果保存在FrameBuffer中。</li>\n<li>显示器在下一个显示周期到来时将FrameBuffer中的数据显示到屏幕上。<br>以上就是UIImageView在iOS平台上显示出来的基本过程。</li>\n</ol>\n<p>当然,具体的显示过程是非常复杂的,尤其是在涉及到一些特效或动画时。</p>\n<p>参考:</p>\n<ul>\n<li><a href=\"https://objccn.io/issue-3-1/\">绘制像素到屏幕上</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/\">iOS 保持界面流畅的技巧</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/02/ios_image_tips/\">iOS 处理图片的一些小 Tip</a></li>\n<li><a href=\"http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/\">移动端图片格式调研</a></li>\n<li><a href=\"http://iosres.com\">iOS Resource</a></li>\n<li><a href=\"https://developer.apple.com/videos/play/wwdc2014/419/\">Advanced Graphics and Animations for iOS Apps</a></li>\n<li><a href=\"http://stackoverflow.com/questions/7558636/ios-is-core-graphics-implemented-on-top-of-opengl\">iOS: is Core Graphics implemented on top of OpenGL?</a></li>\n<li><a href=\"https://learnopengl.com\">学习OpenGL</a></li>\n</ul>\n<p><code>文中若有不正确的地方,烦请大家指正。</code></p>\n"},{"title":"一种新的代码组织办法 feature flow","date":"2017-02-07T16:00:00.000Z","author":"张琦","_content":"![ ](http://o4a7cbihz.qnssl.com/cover/a5cad079-8630-462f-98b6-6c634d535eb6)\n当前大部分团队都使用git flow流程管理代码(不了解gitflow的朋友可以自行搜索),以我们团队的状况来看,一个产品的开发初期是比较适合用git flow的,因为初期都是在做功能添加,而且人比较少,每个迭代只更新一个或几个完整的功能。到了后期的功能调整阶段,git flow就不太适用了,人员多了就可以做更多的事,可能有多个模块需要调整,多个版本同时在开发。\n\n我们的前端组遇到过这样的情况,在同一个仓库中,三个人同时进行开发,做不同的功能,一个人配合客户端修改JSBridge,一个人给运营做活动页面,还有一个人在开发新功能。其中配合客户端的修改要和客户端一起上线,活动页面要按运营的时间表上线,新功能要等服务端部署上线,这就有三个上线时间点。按照gitflow的流程管理,开发完毕的feature都合并到develop,发布的时候我们遇到了困难,客户端准备上线了,需要将此仓库发布,但是活动页面还没到活动的日期,不能让用户提前看到。为了解决这个问题,我们只好在几个修改的地方都加上了版本号判断,类似的问题也在服务端和客户端的发布中出现过。\n\n为了规避这种问题,我们制定了一种新的代码管理流程,我们称之为feature flow\n\n### 主要分支\n- master\n- develop\n\n<strong>master分支</strong>用于发布版本,在发布之前将已经测试完毕的feature和bugfix合并到master分支,不允许从develop分支直接合并到master\n\n<strong>develop分支</strong>是众多feature和bugfix的合集,代码杂乱不可直接合并到develop,而且不允许在develop分支上commit代码\n\n### feature\n代码管理以feature为核心,例如开发一个feature A,则从master拉一个分支featureA出来,在featureA上开发完成后合并到develop分支,发测试包测试,如有问题则继续在featureA上修复,然后合并develop分支再发包测试,测试完毕后featureA放在那里等待发布。\n\n当决定发布一个新版本时,会挑选多个feature,这些feature分支都是开发并测试完毕的,将它们合并到master之后删除,在master上发布。\n\n这样做的原因是现在一般都是多个版本的开发同时进行,不能确定某个版本发布时会包含哪些feature,若像之前一样git flow管理的话,合并到develop的feature想摘出来会十分困难\n\n### bugfix\nbug修复分两种情况进行管理\n\n- 简单bug,指修复后容易测试,可确认修复不会造成其它问题\n- 复杂bug,修复后不容易测试,有隐患带来其它问题,需长时间测试或认真考虑在哪个版本发布的问题\n\n简单bug修复不必遵循feature管理的原则,在develop分支测试完成后直接合并到master分支\n复杂bug修复和feature管理做相同处理\n\n\n### feature分支过多\n如果feature本身小而数量多,可以将确定会一起发布的feature合并到一个存储分支,比如一个版本号分支2.0.6","source":"_posts/一种新的代码组织办法 feature flow.md","raw":"---\ntitle: 一种新的代码组织办法 feature flow\ndate: 2017-02-08\ncategory: ios\nauthor: 张琦\ntags: feature-flow\n---\n![ ](http://o4a7cbihz.qnssl.com/cover/a5cad079-8630-462f-98b6-6c634d535eb6)\n当前大部分团队都使用git flow流程管理代码(不了解gitflow的朋友可以自行搜索),以我们团队的状况来看,一个产品的开发初期是比较适合用git flow的,因为初期都是在做功能添加,而且人比较少,每个迭代只更新一个或几个完整的功能。到了后期的功能调整阶段,git flow就不太适用了,人员多了就可以做更多的事,可能有多个模块需要调整,多个版本同时在开发。\n\n我们的前端组遇到过这样的情况,在同一个仓库中,三个人同时进行开发,做不同的功能,一个人配合客户端修改JSBridge,一个人给运营做活动页面,还有一个人在开发新功能。其中配合客户端的修改要和客户端一起上线,活动页面要按运营的时间表上线,新功能要等服务端部署上线,这就有三个上线时间点。按照gitflow的流程管理,开发完毕的feature都合并到develop,发布的时候我们遇到了困难,客户端准备上线了,需要将此仓库发布,但是活动页面还没到活动的日期,不能让用户提前看到。为了解决这个问题,我们只好在几个修改的地方都加上了版本号判断,类似的问题也在服务端和客户端的发布中出现过。\n\n为了规避这种问题,我们制定了一种新的代码管理流程,我们称之为feature flow\n\n### 主要分支\n- master\n- develop\n\n<strong>master分支</strong>用于发布版本,在发布之前将已经测试完毕的feature和bugfix合并到master分支,不允许从develop分支直接合并到master\n\n<strong>develop分支</strong>是众多feature和bugfix的合集,代码杂乱不可直接合并到develop,而且不允许在develop分支上commit代码\n\n### feature\n代码管理以feature为核心,例如开发一个feature A,则从master拉一个分支featureA出来,在featureA上开发完成后合并到develop分支,发测试包测试,如有问题则继续在featureA上修复,然后合并develop分支再发包测试,测试完毕后featureA放在那里等待发布。\n\n当决定发布一个新版本时,会挑选多个feature,这些feature分支都是开发并测试完毕的,将它们合并到master之后删除,在master上发布。\n\n这样做的原因是现在一般都是多个版本的开发同时进行,不能确定某个版本发布时会包含哪些feature,若像之前一样git flow管理的话,合并到develop的feature想摘出来会十分困难\n\n### bugfix\nbug修复分两种情况进行管理\n\n- 简单bug,指修复后容易测试,可确认修复不会造成其它问题\n- 复杂bug,修复后不容易测试,有隐患带来其它问题,需长时间测试或认真考虑在哪个版本发布的问题\n\n简单bug修复不必遵循feature管理的原则,在develop分支测试完成后直接合并到master分支\n复杂bug修复和feature管理做相同处理\n\n\n### feature分支过多\n如果feature本身小而数量多,可以将确定会一起发布的feature合并到一个存储分支,比如一个版本号分支2.0.6","slug":"一种新的代码组织办法 feature flow","published":1,"updated":"2017-02-21T10:01:13.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72t0005as5fkflvt8di","content":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/a5cad079-8630-462f-98b6-6c634d535eb6\" alt=\" \"><br>当前大部分团队都使用git flow流程管理代码(不了解gitflow的朋友可以自行搜索),以我们团队的状况来看,一个产品的开发初期是比较适合用git flow的,因为初期都是在做功能添加,而且人比较少,每个迭代只更新一个或几个完整的功能。到了后期的功能调整阶段,git flow就不太适用了,人员多了就可以做更多的事,可能有多个模块需要调整,多个版本同时在开发。</p>\n<p>我们的前端组遇到过这样的情况,在同一个仓库中,三个人同时进行开发,做不同的功能,一个人配合客户端修改JSBridge,一个人给运营做活动页面,还有一个人在开发新功能。其中配合客户端的修改要和客户端一起上线,活动页面要按运营的时间表上线,新功能要等服务端部署上线,这就有三个上线时间点。按照gitflow的流程管理,开发完毕的feature都合并到develop,发布的时候我们遇到了困难,客户端准备上线了,需要将此仓库发布,但是活动页面还没到活动的日期,不能让用户提前看到。为了解决这个问题,我们只好在几个修改的地方都加上了版本号判断,类似的问题也在服务端和客户端的发布中出现过。</p>\n<p>为了规避这种问题,我们制定了一种新的代码管理流程,我们称之为feature flow</p>\n<h3 id=\"主要分支\"><a href=\"#主要分支\" class=\"headerlink\" title=\"主要分支\"></a>主要分支</h3><ul>\n<li>master</li>\n<li>develop</li>\n</ul>\n<p><strong>master分支</strong>用于发布版本,在发布之前将已经测试完毕的feature和bugfix合并到master分支,不允许从develop分支直接合并到master</p>\n<p><strong>develop分支</strong>是众多feature和bugfix的合集,代码杂乱不可直接合并到develop,而且不允许在develop分支上commit代码</p>\n<h3 id=\"feature\"><a href=\"#feature\" class=\"headerlink\" title=\"feature\"></a>feature</h3><p>代码管理以feature为核心,例如开发一个feature A,则从master拉一个分支featureA出来,在featureA上开发完成后合并到develop分支,发测试包测试,如有问题则继续在featureA上修复,然后合并develop分支再发包测试,测试完毕后featureA放在那里等待发布。</p>\n<p>当决定发布一个新版本时,会挑选多个feature,这些feature分支都是开发并测试完毕的,将它们合并到master之后删除,在master上发布。</p>\n<p>这样做的原因是现在一般都是多个版本的开发同时进行,不能确定某个版本发布时会包含哪些feature,若像之前一样git flow管理的话,合并到develop的feature想摘出来会十分困难</p>\n<h3 id=\"bugfix\"><a href=\"#bugfix\" class=\"headerlink\" title=\"bugfix\"></a>bugfix</h3><p>bug修复分两种情况进行管理</p>\n<ul>\n<li>简单bug,指修复后容易测试,可确认修复不会造成其它问题</li>\n<li>复杂bug,修复后不容易测试,有隐患带来其它问题,需长时间测试或认真考虑在哪个版本发布的问题</li>\n</ul>\n<p>简单bug修复不必遵循feature管理的原则,在develop分支测试完成后直接合并到master分支<br>复杂bug修复和feature管理做相同处理</p>\n<h3 id=\"feature分支过多\"><a href=\"#feature分支过多\" class=\"headerlink\" title=\"feature分支过多\"></a>feature分支过多</h3><p>如果feature本身小而数量多,可以将确定会一起发布的feature合并到一个存储分支,比如一个版本号分支2.0.6</p>\n","excerpt":"","more":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/a5cad079-8630-462f-98b6-6c634d535eb6\" alt=\" \"><br>当前大部分团队都使用git flow流程管理代码(不了解gitflow的朋友可以自行搜索),以我们团队的状况来看,一个产品的开发初期是比较适合用git flow的,因为初期都是在做功能添加,而且人比较少,每个迭代只更新一个或几个完整的功能。到了后期的功能调整阶段,git flow就不太适用了,人员多了就可以做更多的事,可能有多个模块需要调整,多个版本同时在开发。</p>\n<p>我们的前端组遇到过这样的情况,在同一个仓库中,三个人同时进行开发,做不同的功能,一个人配合客户端修改JSBridge,一个人给运营做活动页面,还有一个人在开发新功能。其中配合客户端的修改要和客户端一起上线,活动页面要按运营的时间表上线,新功能要等服务端部署上线,这就有三个上线时间点。按照gitflow的流程管理,开发完毕的feature都合并到develop,发布的时候我们遇到了困难,客户端准备上线了,需要将此仓库发布,但是活动页面还没到活动的日期,不能让用户提前看到。为了解决这个问题,我们只好在几个修改的地方都加上了版本号判断,类似的问题也在服务端和客户端的发布中出现过。</p>\n<p>为了规避这种问题,我们制定了一种新的代码管理流程,我们称之为feature flow</p>\n<h3 id=\"主要分支\"><a href=\"#主要分支\" class=\"headerlink\" title=\"主要分支\"></a>主要分支</h3><ul>\n<li>master</li>\n<li>develop</li>\n</ul>\n<p><strong>master分支</strong>用于发布版本,在发布之前将已经测试完毕的feature和bugfix合并到master分支,不允许从develop分支直接合并到master</p>\n<p><strong>develop分支</strong>是众多feature和bugfix的合集,代码杂乱不可直接合并到develop,而且不允许在develop分支上commit代码</p>\n<h3 id=\"feature\"><a href=\"#feature\" class=\"headerlink\" title=\"feature\"></a>feature</h3><p>代码管理以feature为核心,例如开发一个feature A,则从master拉一个分支featureA出来,在featureA上开发完成后合并到develop分支,发测试包测试,如有问题则继续在featureA上修复,然后合并develop分支再发包测试,测试完毕后featureA放在那里等待发布。</p>\n<p>当决定发布一个新版本时,会挑选多个feature,这些feature分支都是开发并测试完毕的,将它们合并到master之后删除,在master上发布。</p>\n<p>这样做的原因是现在一般都是多个版本的开发同时进行,不能确定某个版本发布时会包含哪些feature,若像之前一样git flow管理的话,合并到develop的feature想摘出来会十分困难</p>\n<h3 id=\"bugfix\"><a href=\"#bugfix\" class=\"headerlink\" title=\"bugfix\"></a>bugfix</h3><p>bug修复分两种情况进行管理</p>\n<ul>\n<li>简单bug,指修复后容易测试,可确认修复不会造成其它问题</li>\n<li>复杂bug,修复后不容易测试,有隐患带来其它问题,需长时间测试或认真考虑在哪个版本发布的问题</li>\n</ul>\n<p>简单bug修复不必遵循feature管理的原则,在develop分支测试完成后直接合并到master分支<br>复杂bug修复和feature管理做相同处理</p>\n<h3 id=\"feature分支过多\"><a href=\"#feature分支过多\" class=\"headerlink\" title=\"feature分支过多\"></a>feature分支过多</h3><p>如果feature本身小而数量多,可以将确定会一起发布的feature合并到一个存储分支,比如一个版本号分支2.0.6</p>\n"},{"title":"一个KVO的小TIP","date":"2017-03-20T16:00:00.000Z","author":"张永彬","_content":"\n![](http://o4a7cbihz.qnssl.com/cover/cb780468-9cf4-4472-8035-7d11041a9dcc)\n\n如下面的代码,类 AA 继承自 A,若 A 中也使用了 KVO,实现了`observeValueForKeyPath:ofObject:change:context:`方法,就需要在类 AA 中通过调用`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`方法执行 A 中的实现的`observeValueForKeyPath:ofObject:change:context:`方法,但若类A中没有实现则会因为调用`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`引发崩溃(虽然 NSObject 类的 NSKeyValueObserving 的扩展中定义了`observeValueForKeyPath:ofObject:change:context:`方法,但是却没有实现这个方法)。因此,在使用 KVO 时要注意在继承关系中使用 KVO。\n\n```\n@interface AA: A\n@end\n\n@implementation AA\n- (void)dealloc\n{\n [self.xx removeObserver:self forKeyPath:@\"xxxx\"];\n}\n\n- (void)addObserver {\n [self.xx addObserver:self forKeyPath:@\"xxxx\" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context\n{\n if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {\n [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];\n }\n\n if ([object isEqual:self.xx] && [keyPath isEqualToString:@\"xxxx\"]) {\n // TODO\n }\n}\n@endif\n```\n\n### 解决方法:\n1.添加一个 NSObject 的扩展,实现`observeValueForKeyPath:ofObject:change:context:`空方法。这样通过`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`的方式调用就不会出问题了。\n\n2.添加一个单独的 KVO 的 Observer 类。\n (1)在 Observer 类中实现`observeValueForKeyPath:ofObject:change:context:`方法,\n (2)通过 Block 的方式将`处理KVO通知的代码`在具体业务逻辑中实现,然后传给Observer类,\n (3)Observer 类收到KVO通知调用 object 对应的 block(s) 就可以了。这样的好处是只用在 Observer 类中实现`observeValueForKeyPath:ofObject:change:context:`方法,具体的业务中只负责实现`处理KVO通知的代码`,实现了代码分离;同一个 object 可以有多个 block。\n\n 另:可以把 Observer 实现为一个单例([FBKVOController](http://github.com/facebook/KVOController)),也可以以 runtime 的方式为 NSObject 增加一个字典变量,以 KVO 的 keyPath 为key,Observer 对象为 value,实现一个简单实用的 KVO 自定义方案([YYKit](https://github.com/ibireme/YYKit))。\n\n### KVO注意事项:\n 由于 KVO 天生的娇惯特质(1. 必须先 addObserver,再 removeObserver;2. 在 receiver 释放后必须同时 remove 掉其对应的所有 Observers),在多线程(异步)的情况下可能不能保证 add 与 remove 的顺序,以及开发者忘记在写 removeObserver 方法等原因造成 App 崩溃。因此在编写 KVO 代码时需要十分小心。\n\n 可参考 [FBKVOController](http://github.com/facebook/KVOController) 中对 KVO 的处理。\n","source":"_posts/在继承关系的类中使用KVO引发的问题.md","raw":"---\ntitle: 一个KVO的小TIP\ndate: 2017-03-21\ncategory: ios\nauthor: 张永彬\n---\n\n![](http://o4a7cbihz.qnssl.com/cover/cb780468-9cf4-4472-8035-7d11041a9dcc)\n\n如下面的代码,类 AA 继承自 A,若 A 中也使用了 KVO,实现了`observeValueForKeyPath:ofObject:change:context:`方法,就需要在类 AA 中通过调用`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`方法执行 A 中的实现的`observeValueForKeyPath:ofObject:change:context:`方法,但若类A中没有实现则会因为调用`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`引发崩溃(虽然 NSObject 类的 NSKeyValueObserving 的扩展中定义了`observeValueForKeyPath:ofObject:change:context:`方法,但是却没有实现这个方法)。因此,在使用 KVO 时要注意在继承关系中使用 KVO。\n\n```\n@interface AA: A\n@end\n\n@implementation AA\n- (void)dealloc\n{\n [self.xx removeObserver:self forKeyPath:@\"xxxx\"];\n}\n\n- (void)addObserver {\n [self.xx addObserver:self forKeyPath:@\"xxxx\" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];\n}\n\n- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context\n{\n if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {\n [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];\n }\n\n if ([object isEqual:self.xx] && [keyPath isEqualToString:@\"xxxx\"]) {\n // TODO\n }\n}\n@endif\n```\n\n### 解决方法:\n1.添加一个 NSObject 的扩展,实现`observeValueForKeyPath:ofObject:change:context:`空方法。这样通过`[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];`的方式调用就不会出问题了。\n\n2.添加一个单独的 KVO 的 Observer 类。\n (1)在 Observer 类中实现`observeValueForKeyPath:ofObject:change:context:`方法,\n (2)通过 Block 的方式将`处理KVO通知的代码`在具体业务逻辑中实现,然后传给Observer类,\n (3)Observer 类收到KVO通知调用 object 对应的 block(s) 就可以了。这样的好处是只用在 Observer 类中实现`observeValueForKeyPath:ofObject:change:context:`方法,具体的业务中只负责实现`处理KVO通知的代码`,实现了代码分离;同一个 object 可以有多个 block。\n\n 另:可以把 Observer 实现为一个单例([FBKVOController](http://github.com/facebook/KVOController)),也可以以 runtime 的方式为 NSObject 增加一个字典变量,以 KVO 的 keyPath 为key,Observer 对象为 value,实现一个简单实用的 KVO 自定义方案([YYKit](https://github.com/ibireme/YYKit))。\n\n### KVO注意事项:\n 由于 KVO 天生的娇惯特质(1. 必须先 addObserver,再 removeObserver;2. 在 receiver 释放后必须同时 remove 掉其对应的所有 Observers),在多线程(异步)的情况下可能不能保证 add 与 remove 的顺序,以及开发者忘记在写 removeObserver 方法等原因造成 App 崩溃。因此在编写 KVO 代码时需要十分小心。\n\n 可参考 [FBKVOController](http://github.com/facebook/KVOController) 中对 KVO 的处理。\n","slug":"在继承关系的类中使用KVO引发的问题","published":1,"updated":"2017-03-28T01:41:35.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72w0006as5fq6rdtrmo","content":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/cb780468-9cf4-4472-8035-7d11041a9dcc\" alt=\"\"></p>\n<p>如下面的代码,类 AA 继承自 A,若 A 中也使用了 KVO,实现了<code>observeValueForKeyPath:ofObject:change:context:</code>方法,就需要在类 AA 中通过调用<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>方法执行 A 中的实现的<code>observeValueForKeyPath:ofObject:change:context:</code>方法,但若类A中没有实现则会因为调用<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>引发崩溃(虽然 NSObject 类的 NSKeyValueObserving 的扩展中定义了<code>observeValueForKeyPath:ofObject:change:context:</code>方法,但是却没有实现这个方法)。因此,在使用 KVO 时要注意在继承关系中使用 KVO。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div></pre></td><td class=\"code\"><pre><div class=\"line\">@interface AA: A</div><div class=\"line\">@end</div><div class=\"line\"></div><div class=\"line\">@implementation AA</div><div class=\"line\">- (void)dealloc</div><div class=\"line\">{</div><div class=\"line\"> [self.xx removeObserver:self forKeyPath:@"xxxx"];</div><div class=\"line\">}</div><div class=\"line\"></div><div class=\"line\">- (void)addObserver {</div><div class=\"line\"> [self.xx addObserver:self forKeyPath:@"xxxx" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];</div><div class=\"line\">}</div><div class=\"line\"></div><div class=\"line\">- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context</div><div class=\"line\">{</div><div class=\"line\"> if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {</div><div class=\"line\"> [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> if ([object isEqual:self.xx] && [keyPath isEqualToString:@"xxxx"]) {</div><div class=\"line\"> // TODO</div><div class=\"line\"> }</div><div class=\"line\">}</div><div class=\"line\">@endif</div></pre></td></tr></table></figure>\n<h3 id=\"解决方法:\"><a href=\"#解决方法:\" class=\"headerlink\" title=\"解决方法:\"></a>解决方法:</h3><p>1.添加一个 NSObject 的扩展,实现<code>observeValueForKeyPath:ofObject:change:context:</code>空方法。这样通过<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>的方式调用就不会出问题了。</p>\n<p>2.添加一个单独的 KVO 的 Observer 类。<br> (1)在 Observer 类中实现<code>observeValueForKeyPath:ofObject:change:context:</code>方法,<br> (2)通过 Block 的方式将<code>处理KVO通知的代码</code>在具体业务逻辑中实现,然后传给Observer类,<br> (3)Observer 类收到KVO通知调用 object 对应的 block(s) 就可以了。这样的好处是只用在 Observer 类中实现<code>observeValueForKeyPath:ofObject:change:context:</code>方法,具体的业务中只负责实现<code>处理KVO通知的代码</code>,实现了代码分离;同一个 object 可以有多个 block。</p>\n<p> 另:可以把 Observer 实现为一个单例(<a href=\"http://github.com/facebook/KVOController\" target=\"_blank\" rel=\"external\">FBKVOController</a>),也可以以 runtime 的方式为 NSObject 增加一个字典变量,以 KVO 的 keyPath 为key,Observer 对象为 value,实现一个简单实用的 KVO 自定义方案(<a href=\"https://github.com/ibireme/YYKit\" target=\"_blank\" rel=\"external\">YYKit</a>)。</p>\n<h3 id=\"KVO注意事项:\"><a href=\"#KVO注意事项:\" class=\"headerlink\" title=\"KVO注意事项:\"></a>KVO注意事项:</h3><p> 由于 KVO 天生的娇惯特质(1. 必须先 addObserver,再 removeObserver;2. 在 receiver 释放后必须同时 remove 掉其对应的所有 Observers),在多线程(异步)的情况下可能不能保证 add 与 remove 的顺序,以及开发者忘记在写 removeObserver 方法等原因造成 App 崩溃。因此在编写 KVO 代码时需要十分小心。</p>\n<p> 可参考 <a href=\"http://github.com/facebook/KVOController\" target=\"_blank\" rel=\"external\">FBKVOController</a> 中对 KVO 的处理。</p>\n","excerpt":"","more":"<p><img src=\"http://o4a7cbihz.qnssl.com/cover/cb780468-9cf4-4472-8035-7d11041a9dcc\" alt=\"\"></p>\n<p>如下面的代码,类 AA 继承自 A,若 A 中也使用了 KVO,实现了<code>observeValueForKeyPath:ofObject:change:context:</code>方法,就需要在类 AA 中通过调用<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>方法执行 A 中的实现的<code>observeValueForKeyPath:ofObject:change:context:</code>方法,但若类A中没有实现则会因为调用<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>引发崩溃(虽然 NSObject 类的 NSKeyValueObserving 的扩展中定义了<code>observeValueForKeyPath:ofObject:change:context:</code>方法,但是却没有实现这个方法)。因此,在使用 KVO 时要注意在继承关系中使用 KVO。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div></pre></td><td class=\"code\"><pre><div class=\"line\">@interface AA: A</div><div class=\"line\">@end</div><div class=\"line\"></div><div class=\"line\">@implementation AA</div><div class=\"line\">- (void)dealloc</div><div class=\"line\">{</div><div class=\"line\"> [self.xx removeObserver:self forKeyPath:@"xxxx"];</div><div class=\"line\">}</div><div class=\"line\"></div><div class=\"line\">- (void)addObserver {</div><div class=\"line\"> [self.xx addObserver:self forKeyPath:@"xxxx" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:NULL];</div><div class=\"line\">}</div><div class=\"line\"></div><div class=\"line\">- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context</div><div class=\"line\">{</div><div class=\"line\"> if ([super respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) {</div><div class=\"line\"> [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> if ([object isEqual:self.xx] && [keyPath isEqualToString:@"xxxx"]) {</div><div class=\"line\"> // TODO</div><div class=\"line\"> }</div><div class=\"line\">}</div><div class=\"line\">@endif</div></pre></td></tr></table></figure>\n<h3 id=\"解决方法:\"><a href=\"#解决方法:\" class=\"headerlink\" title=\"解决方法:\"></a>解决方法:</h3><p>1.添加一个 NSObject 的扩展,实现<code>observeValueForKeyPath:ofObject:change:context:</code>空方法。这样通过<code>[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];</code>的方式调用就不会出问题了。</p>\n<p>2.添加一个单独的 KVO 的 Observer 类。<br> (1)在 Observer 类中实现<code>observeValueForKeyPath:ofObject:change:context:</code>方法,<br> (2)通过 Block 的方式将<code>处理KVO通知的代码</code>在具体业务逻辑中实现,然后传给Observer类,<br> (3)Observer 类收到KVO通知调用 object 对应的 block(s) 就可以了。这样的好处是只用在 Observer 类中实现<code>observeValueForKeyPath:ofObject:change:context:</code>方法,具体的业务中只负责实现<code>处理KVO通知的代码</code>,实现了代码分离;同一个 object 可以有多个 block。</p>\n<p> 另:可以把 Observer 实现为一个单例(<a href=\"http://github.com/facebook/KVOController\">FBKVOController</a>),也可以以 runtime 的方式为 NSObject 增加一个字典变量,以 KVO 的 keyPath 为key,Observer 对象为 value,实现一个简单实用的 KVO 自定义方案(<a href=\"https://github.com/ibireme/YYKit\">YYKit</a>)。</p>\n<h3 id=\"KVO注意事项:\"><a href=\"#KVO注意事项:\" class=\"headerlink\" title=\"KVO注意事项:\"></a>KVO注意事项:</h3><p> 由于 KVO 天生的娇惯特质(1. 必须先 addObserver,再 removeObserver;2. 在 receiver 释放后必须同时 remove 掉其对应的所有 Observers),在多线程(异步)的情况下可能不能保证 add 与 remove 的顺序,以及开发者忘记在写 removeObserver 方法等原因造成 App 崩溃。因此在编写 KVO 代码时需要十分小心。</p>\n<p> 可参考 <a href=\"http://github.com/facebook/KVOController\">FBKVOController</a> 中对 KVO 的处理。</p>\n"},{"title":"几种典型的耦合与解决方法","date":"2017-02-27T16:00:00.000Z","author":"张琦","_content":"\n![耦合.png](http://upload-images.jianshu.io/upload_images/3667125-f08e0982fdcfdfd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n项目的规模增长到一定程度之后会面临很多问题,特别是进入调整期之后,大多数需求是针对已有功能的调整和优化,功能的耦合给开发和测试带来了很多困难。\n\n项目中比较典型的耦合有以下几种:\n\n### 1.组件与环境的耦合\n>`问题`:组件的显示依赖于所处的环境为其提供数据,用户对组件的操作也依赖环境去传达和执行。这导致两个问题,一是承载多个组件的环境会很臃肿,二是多个使用组件的环境都要实现管理组件的逻辑。\n\n![1-1.png](http://upload-images.jianshu.io/upload_images/3667125-db4bd74f47e36cae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将组件的管理逻辑封装成管理类,这个类要解决两个问题,一是能够提供组件所需要的所有数据,二是能够执行组件相关的所有操作。\n\n![1-2.png](http://upload-images.jianshu.io/upload_images/3667125-b2b10a844cafd1f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 2.数据的显示与操作的耦合\n>`问题`:同一条数据会映射到多种组件中,不同的操作也会对同一条数据进行不同的修改。这导致一个操作需要映射到多种组件,多个操作就会产生N*N的映射逻辑。修改某一处操作或者显示都会导致大量映射逻辑的修改,影响范围大且测试难度大。\n\n![2-1.png](http://upload-images.jianshu.io/upload_images/3667125-0b43928c28bb60f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将映射逻辑下沉,即放到数据库框架中进行。操作的结果不再去直接映射到各个组件中的数据,而是写入数据库,由数据库通知相应的组件数据变更,各个组件只根据数据库中的数据进行显示,不再接收其它地方的数据映射。\n\n![2-2.png](http://upload-images.jianshu.io/upload_images/3667125-8f8104ea0af60d12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 3.操作的发起与结果的耦合\n>`问题`:不同的组件或者组件管理类发起同一个操作,并各自处理操作的结果。这会导致处理操作结果的逻辑冗余且可能有差异,当操作发生变动时就需要多处修改。\n\n![3-1.png](http://upload-images.jianshu.io/upload_images/3667125-2c648f8df485cb48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:组件只负责发起操作,结果处理在统一的操作类中处理,包括结果的解析与写库。这样有一个额外的好处,组件发起操作一般是在主线程,因为我们不再需要对结果进行映射(也需要在主线程),我们可以在操作类中以后台线程处理结果。\n\n![3-2.png](http://upload-images.jianshu.io/upload_images/3667125-44af0705151ea394.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 4.模块健壮性耦合\n>`问题`:一个模块的健壮性依赖于其它模块的健壮性,这里所说的健壮性包括模块的输入、内部逻辑与输出的健壮性。当上游模块被修改时,无法保证下游模块的健壮。\n\n![4-1.png](http://upload-images.jianshu.io/upload_images/3667125-0f1447bee68153d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:每个模块要能够独自保持自身的安全,不用因为其它模块的修改而重新进行健壮性测试。\n\n![4-2.png](http://upload-images.jianshu.io/upload_images/3667125-16fc6ab5bda7a9eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 5.模块依赖耦合\n>`问题`:多个模块之间存在互相调用的关系,模块之间互相依赖,当某一模块被修改时,与该模块有调用关系的相关模块可能都需要修改与测试,也即无法确定某一模块修改的影响范围。\n\n![5-1.png](http://upload-images.jianshu.io/upload_images/3667125-bda4941752426ef7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将模块之间的依赖关系下沉,以一个公共模块处理依赖和调用逻辑,这也是业界比较火的概念“组件化”,从软件工程理论上说这是针对接口编程而非针对实现编程。\n\n![5-2.png](http://upload-images.jianshu.io/upload_images/3667125-e970d161505cae63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解耦合是我们改善项目组织结构的第一步,项目实现解耦合之后会在可修改性和可测试性上有很大改善。","source":"_posts/几种典型的耦合与解决方法.md","raw":"---\ntitle: 几种典型的耦合与解决方法\ndate: 2017-02-28\ncategory: ios\nauthor: 张琦\n---\n\n![耦合.png](http://upload-images.jianshu.io/upload_images/3667125-f08e0982fdcfdfd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n项目的规模增长到一定程度之后会面临很多问题,特别是进入调整期之后,大多数需求是针对已有功能的调整和优化,功能的耦合给开发和测试带来了很多困难。\n\n项目中比较典型的耦合有以下几种:\n\n### 1.组件与环境的耦合\n>`问题`:组件的显示依赖于所处的环境为其提供数据,用户对组件的操作也依赖环境去传达和执行。这导致两个问题,一是承载多个组件的环境会很臃肿,二是多个使用组件的环境都要实现管理组件的逻辑。\n\n![1-1.png](http://upload-images.jianshu.io/upload_images/3667125-db4bd74f47e36cae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将组件的管理逻辑封装成管理类,这个类要解决两个问题,一是能够提供组件所需要的所有数据,二是能够执行组件相关的所有操作。\n\n![1-2.png](http://upload-images.jianshu.io/upload_images/3667125-b2b10a844cafd1f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 2.数据的显示与操作的耦合\n>`问题`:同一条数据会映射到多种组件中,不同的操作也会对同一条数据进行不同的修改。这导致一个操作需要映射到多种组件,多个操作就会产生N*N的映射逻辑。修改某一处操作或者显示都会导致大量映射逻辑的修改,影响范围大且测试难度大。\n\n![2-1.png](http://upload-images.jianshu.io/upload_images/3667125-0b43928c28bb60f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将映射逻辑下沉,即放到数据库框架中进行。操作的结果不再去直接映射到各个组件中的数据,而是写入数据库,由数据库通知相应的组件数据变更,各个组件只根据数据库中的数据进行显示,不再接收其它地方的数据映射。\n\n![2-2.png](http://upload-images.jianshu.io/upload_images/3667125-8f8104ea0af60d12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 3.操作的发起与结果的耦合\n>`问题`:不同的组件或者组件管理类发起同一个操作,并各自处理操作的结果。这会导致处理操作结果的逻辑冗余且可能有差异,当操作发生变动时就需要多处修改。\n\n![3-1.png](http://upload-images.jianshu.io/upload_images/3667125-2c648f8df485cb48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:组件只负责发起操作,结果处理在统一的操作类中处理,包括结果的解析与写库。这样有一个额外的好处,组件发起操作一般是在主线程,因为我们不再需要对结果进行映射(也需要在主线程),我们可以在操作类中以后台线程处理结果。\n\n![3-2.png](http://upload-images.jianshu.io/upload_images/3667125-44af0705151ea394.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 4.模块健壮性耦合\n>`问题`:一个模块的健壮性依赖于其它模块的健壮性,这里所说的健壮性包括模块的输入、内部逻辑与输出的健壮性。当上游模块被修改时,无法保证下游模块的健壮。\n\n![4-1.png](http://upload-images.jianshu.io/upload_images/3667125-0f1447bee68153d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:每个模块要能够独自保持自身的安全,不用因为其它模块的修改而重新进行健壮性测试。\n\n![4-2.png](http://upload-images.jianshu.io/upload_images/3667125-16fc6ab5bda7a9eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 5.模块依赖耦合\n>`问题`:多个模块之间存在互相调用的关系,模块之间互相依赖,当某一模块被修改时,与该模块有调用关系的相关模块可能都需要修改与测试,也即无法确定某一模块修改的影响范围。\n\n![5-1.png](http://upload-images.jianshu.io/upload_images/3667125-bda4941752426ef7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n>`解决方法`:将模块之间的依赖关系下沉,以一个公共模块处理依赖和调用逻辑,这也是业界比较火的概念“组件化”,从软件工程理论上说这是针对接口编程而非针对实现编程。\n\n![5-2.png](http://upload-images.jianshu.io/upload_images/3667125-e970d161505cae63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n解耦合是我们改善项目组织结构的第一步,项目实现解耦合之后会在可修改性和可测试性上有很大改善。","slug":"几种典型的耦合与解决方法","published":1,"updated":"2017-02-28T01:22:50.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab72z000aas5fwwwa8bbu","content":"<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-f08e0982fdcfdfd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"耦合.png\"></p>\n<p>项目的规模增长到一定程度之后会面临很多问题,特别是进入调整期之后,大多数需求是针对已有功能的调整和优化,功能的耦合给开发和测试带来了很多困难。</p>\n<p>项目中比较典型的耦合有以下几种:</p>\n<h3 id=\"1-组件与环境的耦合\"><a href=\"#1-组件与环境的耦合\" class=\"headerlink\" title=\"1.组件与环境的耦合\"></a>1.组件与环境的耦合</h3><blockquote>\n<p><code>问题</code>:组件的显示依赖于所处的环境为其提供数据,用户对组件的操作也依赖环境去传达和执行。这导致两个问题,一是承载多个组件的环境会很臃肿,二是多个使用组件的环境都要实现管理组件的逻辑。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-db4bd74f47e36cae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"1-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将组件的管理逻辑封装成管理类,这个类要解决两个问题,一是能够提供组件所需要的所有数据,二是能够执行组件相关的所有操作。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-b2b10a844cafd1f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"1-2.png\"></p>\n<h3 id=\"2-数据的显示与操作的耦合\"><a href=\"#2-数据的显示与操作的耦合\" class=\"headerlink\" title=\"2.数据的显示与操作的耦合\"></a>2.数据的显示与操作的耦合</h3><blockquote>\n<p><code>问题</code>:同一条数据会映射到多种组件中,不同的操作也会对同一条数据进行不同的修改。这导致一个操作需要映射到多种组件,多个操作就会产生N*N的映射逻辑。修改某一处操作或者显示都会导致大量映射逻辑的修改,影响范围大且测试难度大。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-0b43928c28bb60f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"2-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将映射逻辑下沉,即放到数据库框架中进行。操作的结果不再去直接映射到各个组件中的数据,而是写入数据库,由数据库通知相应的组件数据变更,各个组件只根据数据库中的数据进行显示,不再接收其它地方的数据映射。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-8f8104ea0af60d12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"2-2.png\"></p>\n<h3 id=\"3-操作的发起与结果的耦合\"><a href=\"#3-操作的发起与结果的耦合\" class=\"headerlink\" title=\"3.操作的发起与结果的耦合\"></a>3.操作的发起与结果的耦合</h3><blockquote>\n<p><code>问题</code>:不同的组件或者组件管理类发起同一个操作,并各自处理操作的结果。这会导致处理操作结果的逻辑冗余且可能有差异,当操作发生变动时就需要多处修改。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-2c648f8df485cb48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"3-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:组件只负责发起操作,结果处理在统一的操作类中处理,包括结果的解析与写库。这样有一个额外的好处,组件发起操作一般是在主线程,因为我们不再需要对结果进行映射(也需要在主线程),我们可以在操作类中以后台线程处理结果。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-44af0705151ea394.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"3-2.png\"></p>\n<h3 id=\"4-模块健壮性耦合\"><a href=\"#4-模块健壮性耦合\" class=\"headerlink\" title=\"4.模块健壮性耦合\"></a>4.模块健壮性耦合</h3><blockquote>\n<p><code>问题</code>:一个模块的健壮性依赖于其它模块的健壮性,这里所说的健壮性包括模块的输入、内部逻辑与输出的健壮性。当上游模块被修改时,无法保证下游模块的健壮。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-0f1447bee68153d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"4-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:每个模块要能够独自保持自身的安全,不用因为其它模块的修改而重新进行健壮性测试。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-16fc6ab5bda7a9eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"4-2.png\"></p>\n<h3 id=\"5-模块依赖耦合\"><a href=\"#5-模块依赖耦合\" class=\"headerlink\" title=\"5.模块依赖耦合\"></a>5.模块依赖耦合</h3><blockquote>\n<p><code>问题</code>:多个模块之间存在互相调用的关系,模块之间互相依赖,当某一模块被修改时,与该模块有调用关系的相关模块可能都需要修改与测试,也即无法确定某一模块修改的影响范围。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-bda4941752426ef7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"5-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将模块之间的依赖关系下沉,以一个公共模块处理依赖和调用逻辑,这也是业界比较火的概念“组件化”,从软件工程理论上说这是针对接口编程而非针对实现编程。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-e970d161505cae63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"5-2.png\"></p>\n<p>解耦合是我们改善项目组织结构的第一步,项目实现解耦合之后会在可修改性和可测试性上有很大改善。</p>\n","excerpt":"","more":"<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-f08e0982fdcfdfd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"耦合.png\"></p>\n<p>项目的规模增长到一定程度之后会面临很多问题,特别是进入调整期之后,大多数需求是针对已有功能的调整和优化,功能的耦合给开发和测试带来了很多困难。</p>\n<p>项目中比较典型的耦合有以下几种:</p>\n<h3 id=\"1-组件与环境的耦合\"><a href=\"#1-组件与环境的耦合\" class=\"headerlink\" title=\"1.组件与环境的耦合\"></a>1.组件与环境的耦合</h3><blockquote>\n<p><code>问题</code>:组件的显示依赖于所处的环境为其提供数据,用户对组件的操作也依赖环境去传达和执行。这导致两个问题,一是承载多个组件的环境会很臃肿,二是多个使用组件的环境都要实现管理组件的逻辑。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-db4bd74f47e36cae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"1-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将组件的管理逻辑封装成管理类,这个类要解决两个问题,一是能够提供组件所需要的所有数据,二是能够执行组件相关的所有操作。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-b2b10a844cafd1f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"1-2.png\"></p>\n<h3 id=\"2-数据的显示与操作的耦合\"><a href=\"#2-数据的显示与操作的耦合\" class=\"headerlink\" title=\"2.数据的显示与操作的耦合\"></a>2.数据的显示与操作的耦合</h3><blockquote>\n<p><code>问题</code>:同一条数据会映射到多种组件中,不同的操作也会对同一条数据进行不同的修改。这导致一个操作需要映射到多种组件,多个操作就会产生N*N的映射逻辑。修改某一处操作或者显示都会导致大量映射逻辑的修改,影响范围大且测试难度大。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-0b43928c28bb60f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"2-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将映射逻辑下沉,即放到数据库框架中进行。操作的结果不再去直接映射到各个组件中的数据,而是写入数据库,由数据库通知相应的组件数据变更,各个组件只根据数据库中的数据进行显示,不再接收其它地方的数据映射。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-8f8104ea0af60d12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"2-2.png\"></p>\n<h3 id=\"3-操作的发起与结果的耦合\"><a href=\"#3-操作的发起与结果的耦合\" class=\"headerlink\" title=\"3.操作的发起与结果的耦合\"></a>3.操作的发起与结果的耦合</h3><blockquote>\n<p><code>问题</code>:不同的组件或者组件管理类发起同一个操作,并各自处理操作的结果。这会导致处理操作结果的逻辑冗余且可能有差异,当操作发生变动时就需要多处修改。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-2c648f8df485cb48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"3-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:组件只负责发起操作,结果处理在统一的操作类中处理,包括结果的解析与写库。这样有一个额外的好处,组件发起操作一般是在主线程,因为我们不再需要对结果进行映射(也需要在主线程),我们可以在操作类中以后台线程处理结果。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-44af0705151ea394.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"3-2.png\"></p>\n<h3 id=\"4-模块健壮性耦合\"><a href=\"#4-模块健壮性耦合\" class=\"headerlink\" title=\"4.模块健壮性耦合\"></a>4.模块健壮性耦合</h3><blockquote>\n<p><code>问题</code>:一个模块的健壮性依赖于其它模块的健壮性,这里所说的健壮性包括模块的输入、内部逻辑与输出的健壮性。当上游模块被修改时,无法保证下游模块的健壮。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-0f1447bee68153d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"4-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:每个模块要能够独自保持自身的安全,不用因为其它模块的修改而重新进行健壮性测试。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-16fc6ab5bda7a9eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"4-2.png\"></p>\n<h3 id=\"5-模块依赖耦合\"><a href=\"#5-模块依赖耦合\" class=\"headerlink\" title=\"5.模块依赖耦合\"></a>5.模块依赖耦合</h3><blockquote>\n<p><code>问题</code>:多个模块之间存在互相调用的关系,模块之间互相依赖,当某一模块被修改时,与该模块有调用关系的相关模块可能都需要修改与测试,也即无法确定某一模块修改的影响范围。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-bda4941752426ef7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"5-1.png\"></p>\n<blockquote>\n<p><code>解决方法</code>:将模块之间的依赖关系下沉,以一个公共模块处理依赖和调用逻辑,这也是业界比较火的概念“组件化”,从软件工程理论上说这是针对接口编程而非针对实现编程。</p>\n</blockquote>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/3667125-e970d161505cae63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"5-2.png\"></p>\n<p>解耦合是我们改善项目组织结构的第一步,项目实现解耦合之后会在可修改性和可测试性上有很大改善。</p>\n"},{"title":"直播音频小库 voiceLive.js","date":"2016-12-04T16:00:00.000Z","author":"刘政","_content":"\n最近接到做微信公众号语音直播([一块听听](http://live.tinfinite.com/))的需求。需求大概是这样的:我们使用 socket 接收语音消息,语音消息其实就是一条一条音频地址,然后需要支持**切换**、**(连续)播放**、**暂停**、**进度**、**显示时间**各种交互。如图:\n\n![语音直播](http://o4a7cbihz.qnssl.com/cover/c93bfd19-45a2-43b7-86bf-3edfcd6c9160)\n\n前期,没有考虑太多,直接操作 dom,各种修改 audio 属性,操作他的方法。虽然这样也可以满足需求达到目的,但是**频繁的操作 dom**,**兼容性问题**,**高级特性不易用上**等一系列问题,让代码可维护性和可读性都不会太好。\n\n待需求稳定以后,我们打算重构**语音直播**。最终打算使用 howler.js 进行封装成 voiceLive.js 的小库 <https://github.com/lzwaiwai/voiceLive.js>。如图:\n![voiceLive.js](http://o3b126ie1.qnssl.com/cover/5e403295-31fb-4173-b88a-46f868c612ac)\n\n### 用法:\n1、首先我们需要约定待初始化的音频数据结构:\n ```javascript\n var datas = [{\n id: 'mmm', // 音频的唯一标识\n src: 'xxxxx', // 音频地址\n time: 16, // 总时间展示\n currentTime: 0 // 当前的播放时间\n }, {\n id: 'nnn',\n src: 'yyyyy',\n time: 25,\n currentTime: 0\n }];\n ```\n \n2、初始化直播音频播放:\n **datas** 为1中的数据; **step** 为一个类似定时器的执行函数,在直播过程中,不断执行该函数; **onload, onloaderror, onplay, onpause, onstop, onend** 为当前正在播放音频的监听回调函数。\n ```javascript\n var vl = new LiveAudio({\n datas: datas, \n step: function (itemId, currentTime, progress) { // for live process, and like a timer \n console.log('step');\n },\n events: { // events for current voice\n onload: function () {\n console.log('onload');\n },\n onloaderror: function () {\n console.log('onloaderror');\n },\n onplay: function () {\n console.log('onplay');\n },\n onpause: function () {\n console.log('onpause');\n },\n onstop: function () {\n console.log('onstop');\n },\n onend: function () {\n console.log('onend');\n }\n }\n });\n ```\n\n3、其他功能:\n\n1) 我们可以在 step 函数增加如下方法进行进度和时间的变化展示:(进度方面,数字不可能非常准确达到100%,所以需要人为变通一下)\n ```javascript\n progress = (progress * 100).toFixed(2) // for to 100\n if (progress > 99) {\n progress = 100.00\n }\n $('#currentTime-' + itemId).text(Math.floor(currentTime) + 's');\n $('#progress-' + itemId).text(progress + '%')\n ```\n \n2) 连续播放:只需要在音频停止的时候,执行播放下一条即可。\n ```javascript\n onend: function () {\n this.playNext(); // for auto play next item\n console.log('onend');\n }\n ```\n\n3) 当有新的音频加入的时候,需要 `vl.addVoice(data)` 添加即可。\n\n最后欢迎提issue: <https://github.com/lzwaiwai/voiceLive.js/issues>","source":"_posts/直播音频小库 voiceLive.js.md","raw":"---\ntitle: 直播音频小库 voiceLive.js\ndate: 2016-12-05\nauthor: 刘政\ncategory: frontend\ntags: live-audio\n---\n\n最近接到做微信公众号语音直播([一块听听](http://live.tinfinite.com/))的需求。需求大概是这样的:我们使用 socket 接收语音消息,语音消息其实就是一条一条音频地址,然后需要支持**切换**、**(连续)播放**、**暂停**、**进度**、**显示时间**各种交互。如图:\n\n![语音直播](http://o4a7cbihz.qnssl.com/cover/c93bfd19-45a2-43b7-86bf-3edfcd6c9160)\n\n前期,没有考虑太多,直接操作 dom,各种修改 audio 属性,操作他的方法。虽然这样也可以满足需求达到目的,但是**频繁的操作 dom**,**兼容性问题**,**高级特性不易用上**等一系列问题,让代码可维护性和可读性都不会太好。\n\n待需求稳定以后,我们打算重构**语音直播**。最终打算使用 howler.js 进行封装成 voiceLive.js 的小库 <https://github.com/lzwaiwai/voiceLive.js>。如图:\n![voiceLive.js](http://o3b126ie1.qnssl.com/cover/5e403295-31fb-4173-b88a-46f868c612ac)\n\n### 用法:\n1、首先我们需要约定待初始化的音频数据结构:\n ```javascript\n var datas = [{\n id: 'mmm', // 音频的唯一标识\n src: 'xxxxx', // 音频地址\n time: 16, // 总时间展示\n currentTime: 0 // 当前的播放时间\n }, {\n id: 'nnn',\n src: 'yyyyy',\n time: 25,\n currentTime: 0\n }];\n ```\n \n2、初始化直播音频播放:\n **datas** 为1中的数据; **step** 为一个类似定时器的执行函数,在直播过程中,不断执行该函数; **onload, onloaderror, onplay, onpause, onstop, onend** 为当前正在播放音频的监听回调函数。\n ```javascript\n var vl = new LiveAudio({\n datas: datas, \n step: function (itemId, currentTime, progress) { // for live process, and like a timer \n console.log('step');\n },\n events: { // events for current voice\n onload: function () {\n console.log('onload');\n },\n onloaderror: function () {\n console.log('onloaderror');\n },\n onplay: function () {\n console.log('onplay');\n },\n onpause: function () {\n console.log('onpause');\n },\n onstop: function () {\n console.log('onstop');\n },\n onend: function () {\n console.log('onend');\n }\n }\n });\n ```\n\n3、其他功能:\n\n1) 我们可以在 step 函数增加如下方法进行进度和时间的变化展示:(进度方面,数字不可能非常准确达到100%,所以需要人为变通一下)\n ```javascript\n progress = (progress * 100).toFixed(2) // for to 100\n if (progress > 99) {\n progress = 100.00\n }\n $('#currentTime-' + itemId).text(Math.floor(currentTime) + 's');\n $('#progress-' + itemId).text(progress + '%')\n ```\n \n2) 连续播放:只需要在音频停止的时候,执行播放下一条即可。\n ```javascript\n onend: function () {\n this.playNext(); // for auto play next item\n console.log('onend');\n }\n ```\n\n3) 当有新的音频加入的时候,需要 `vl.addVoice(data)` 添加即可。\n\n最后欢迎提issue: <https://github.com/lzwaiwai/voiceLive.js/issues>","slug":"直播音频小库 voiceLive.js","published":1,"updated":"2017-02-21T09:54:23.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab732000cas5flam4bq9s","content":"<p>最近接到做微信公众号语音直播(<a href=\"http://live.tinfinite.com/\" target=\"_blank\" rel=\"external\">一块听听</a>)的需求。需求大概是这样的:我们使用 socket 接收语音消息,语音消息其实就是一条一条音频地址,然后需要支持<strong>切换</strong>、<strong>(连续)播放</strong>、<strong>暂停</strong>、<strong>进度</strong>、<strong>显示时间</strong>各种交互。如图:</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/c93bfd19-45a2-43b7-86bf-3edfcd6c9160\" alt=\"语音直播\"></p>\n<p>前期,没有考虑太多,直接操作 dom,各种修改 audio 属性,操作他的方法。虽然这样也可以满足需求达到目的,但是<strong>频繁的操作 dom</strong>,<strong>兼容性问题</strong>,<strong>高级特性不易用上</strong>等一系列问题,让代码可维护性和可读性都不会太好。</p>\n<p>待需求稳定以后,我们打算重构<strong>语音直播</strong>。最终打算使用 howler.js 进行封装成 voiceLive.js 的小库 <a href=\"https://github.com/lzwaiwai/voiceLive.js\" target=\"_blank\" rel=\"external\">https://github.com/lzwaiwai/voiceLive.js</a>。如图:<br><img src=\"http://o3b126ie1.qnssl.com/cover/5e403295-31fb-4173-b88a-46f868c612ac\" alt=\"voiceLive.js\"></p>\n<h3 id=\"用法:\"><a href=\"#用法:\" class=\"headerlink\" title=\"用法:\"></a>用法:</h3><p>1、首先我们需要约定待初始化的音频数据结构:<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> datas = [{</div><div class=\"line\"> <span class=\"attr\">id</span>: <span class=\"string\">'mmm'</span>, <span class=\"comment\">// 音频的唯一标识</span></div><div class=\"line\"> src: <span class=\"string\">'xxxxx'</span>, <span class=\"comment\">// 音频地址</span></div><div class=\"line\"> time: <span class=\"number\">16</span>, <span class=\"comment\">// 总时间展示</span></div><div class=\"line\"> currentTime: <span class=\"number\">0</span> <span class=\"comment\">// 当前的播放时间</span></div><div class=\"line\">}, {</div><div class=\"line\"> <span class=\"attr\">id</span>: <span class=\"string\">'nnn'</span>,</div><div class=\"line\"> <span class=\"attr\">src</span>: <span class=\"string\">'yyyyy'</span>,</div><div class=\"line\"> <span class=\"attr\">time</span>: <span class=\"number\">25</span>,</div><div class=\"line\"> <span class=\"attr\">currentTime</span>: <span class=\"number\">0</span></div><div class=\"line\">}];</div></pre></td></tr></table></figure></p>\n<p>2、初始化直播音频播放:<br> <strong>datas</strong> 为1中的数据; <strong>step</strong> 为一个类似定时器的执行函数,在直播过程中,不断执行该函数; <strong>onload, onloaderror, onplay, onpause, onstop, onend</strong> 为当前正在播放音频的监听回调函数。<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> vl = <span class=\"keyword\">new</span> LiveAudio({</div><div class=\"line\"> <span class=\"attr\">datas</span>: datas, </div><div class=\"line\"> <span class=\"attr\">step</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">itemId, currentTime, progress</span>) </span>{ <span class=\"comment\">// for live process, and like a timer </span></div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'step'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">events</span>: { <span class=\"comment\">// events for current voice</span></div><div class=\"line\"> onload: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onload'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onloaderror</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onloaderror'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onplay</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onplay'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onpause</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onpause'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onstop</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onstop'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onend</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onend'</span>);</div><div class=\"line\"> }</div><div class=\"line\"> }</div><div class=\"line\">});</div></pre></td></tr></table></figure></p>\n<p>3、其他功能:</p>\n<p>1) 我们可以在 step 函数增加如下方法进行进度和时间的变化展示:(进度方面,数字不可能非常准确达到100%,所以需要人为变通一下)<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div></pre></td><td class=\"code\"><pre><div class=\"line\">progress = (progress * <span class=\"number\">100</span>).toFixed(<span class=\"number\">2</span>) <span class=\"comment\">// for to 100</span></div><div class=\"line\"><span class=\"keyword\">if</span> (progress > <span class=\"number\">99</span>) {</div><div class=\"line\"> progress = <span class=\"number\">100.00</span></div><div class=\"line\">}</div><div class=\"line\">$(<span class=\"string\">'#currentTime-'</span> + itemId).text(<span class=\"built_in\">Math</span>.floor(currentTime) + <span class=\"string\">'s'</span>);</div><div class=\"line\">$(<span class=\"string\">'#progress-'</span> + itemId).text(progress + <span class=\"string\">'%'</span>)</div></pre></td></tr></table></figure></p>\n<p>2) 连续播放:只需要在音频停止的时候,执行播放下一条即可。<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">onend: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">this</span>.playNext(); <span class=\"comment\">// for auto play next item</span></div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onend'</span>);</div><div class=\"line\">}</div></pre></td></tr></table></figure></p>\n<p>3) 当有新的音频加入的时候,需要 <code>vl.addVoice(data)</code> 添加即可。</p>\n<p>最后欢迎提issue: <a href=\"https://github.com/lzwaiwai/voiceLive.js/issues\" target=\"_blank\" rel=\"external\">https://github.com/lzwaiwai/voiceLive.js/issues</a></p>\n","excerpt":"","more":"<p>最近接到做微信公众号语音直播(<a href=\"http://live.tinfinite.com/\">一块听听</a>)的需求。需求大概是这样的:我们使用 socket 接收语音消息,语音消息其实就是一条一条音频地址,然后需要支持<strong>切换</strong>、<strong>(连续)播放</strong>、<strong>暂停</strong>、<strong>进度</strong>、<strong>显示时间</strong>各种交互。如图:</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/c93bfd19-45a2-43b7-86bf-3edfcd6c9160\" alt=\"语音直播\"></p>\n<p>前期,没有考虑太多,直接操作 dom,各种修改 audio 属性,操作他的方法。虽然这样也可以满足需求达到目的,但是<strong>频繁的操作 dom</strong>,<strong>兼容性问题</strong>,<strong>高级特性不易用上</strong>等一系列问题,让代码可维护性和可读性都不会太好。</p>\n<p>待需求稳定以后,我们打算重构<strong>语音直播</strong>。最终打算使用 howler.js 进行封装成 voiceLive.js 的小库 <a href=\"https://github.com/lzwaiwai/voiceLive.js\">https://github.com/lzwaiwai/voiceLive.js</a>。如图:<br><img src=\"http://o3b126ie1.qnssl.com/cover/5e403295-31fb-4173-b88a-46f868c612ac\" alt=\"voiceLive.js\"></p>\n<h3 id=\"用法:\"><a href=\"#用法:\" class=\"headerlink\" title=\"用法:\"></a>用法:</h3><p>1、首先我们需要约定待初始化的音频数据结构:<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> datas = [{</div><div class=\"line\"> <span class=\"attr\">id</span>: <span class=\"string\">'mmm'</span>, <span class=\"comment\">// 音频的唯一标识</span></div><div class=\"line\"> src: <span class=\"string\">'xxxxx'</span>, <span class=\"comment\">// 音频地址</span></div><div class=\"line\"> time: <span class=\"number\">16</span>, <span class=\"comment\">// 总时间展示</span></div><div class=\"line\"> currentTime: <span class=\"number\">0</span> <span class=\"comment\">// 当前的播放时间</span></div><div class=\"line\">}, {</div><div class=\"line\"> <span class=\"attr\">id</span>: <span class=\"string\">'nnn'</span>,</div><div class=\"line\"> <span class=\"attr\">src</span>: <span class=\"string\">'yyyyy'</span>,</div><div class=\"line\"> <span class=\"attr\">time</span>: <span class=\"number\">25</span>,</div><div class=\"line\"> <span class=\"attr\">currentTime</span>: <span class=\"number\">0</span></div><div class=\"line\">}];</div></pre></td></tr></table></figure></p>\n<p>2、初始化直播音频播放:<br> <strong>datas</strong> 为1中的数据; <strong>step</strong> 为一个类似定时器的执行函数,在直播过程中,不断执行该函数; <strong>onload, onloaderror, onplay, onpause, onstop, onend</strong> 为当前正在播放音频的监听回调函数。<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> vl = <span class=\"keyword\">new</span> LiveAudio({</div><div class=\"line\"> <span class=\"attr\">datas</span>: datas, </div><div class=\"line\"> <span class=\"attr\">step</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">itemId, currentTime, progress</span>) </span>{ <span class=\"comment\">// for live process, and like a timer </span></div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'step'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">events</span>: { <span class=\"comment\">// events for current voice</span></div><div class=\"line\"> onload: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onload'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onloaderror</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onloaderror'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onplay</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onplay'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onpause</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onpause'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onstop</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onstop'</span>);</div><div class=\"line\"> },</div><div class=\"line\"> <span class=\"attr\">onend</span>: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onend'</span>);</div><div class=\"line\"> }</div><div class=\"line\"> }</div><div class=\"line\">});</div></pre></td></tr></table></figure></p>\n<p>3、其他功能:</p>\n<p>1) 我们可以在 step 函数增加如下方法进行进度和时间的变化展示:(进度方面,数字不可能非常准确达到100%,所以需要人为变通一下)<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div></pre></td><td class=\"code\"><pre><div class=\"line\">progress = (progress * <span class=\"number\">100</span>).toFixed(<span class=\"number\">2</span>) <span class=\"comment\">// for to 100</span></div><div class=\"line\"><span class=\"keyword\">if</span> (progress > <span class=\"number\">99</span>) {</div><div class=\"line\"> progress = <span class=\"number\">100.00</span></div><div class=\"line\">}</div><div class=\"line\">$(<span class=\"string\">'#currentTime-'</span> + itemId).text(<span class=\"built_in\">Math</span>.floor(currentTime) + <span class=\"string\">'s'</span>);</div><div class=\"line\">$(<span class=\"string\">'#progress-'</span> + itemId).text(progress + <span class=\"string\">'%'</span>)</div></pre></td></tr></table></figure></p>\n<p>2) 连续播放:只需要在音频停止的时候,执行播放下一条即可。<br> <figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div></pre></td><td class=\"code\"><pre><div class=\"line\">onend: <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">this</span>.playNext(); <span class=\"comment\">// for auto play next item</span></div><div class=\"line\"> <span class=\"built_in\">console</span>.log(<span class=\"string\">'onend'</span>);</div><div class=\"line\">}</div></pre></td></tr></table></figure></p>\n<p>3) 当有新的音频加入的时候,需要 <code>vl.addVoice(data)</code> 添加即可。</p>\n<p>最后欢迎提issue: <a href=\"https://github.com/lzwaiwai/voiceLive.js/issues\">https://github.com/lzwaiwai/voiceLive.js/issues</a></p>\n"},{"title":"降低微信页面下拉露底(橡皮筋效果)的几率","date":"2017-03-01T16:00:00.000Z","author":"刘政","_content":"\n当我们开发H5页面的时候,因为 webView 的原因,页面在滚动到页面顶部或底部的时候,容易触发全局的 “橡皮筋效果”。\n\n当页面内部还有滚动的时候,这个效果就会影响到 H5 自己的一些交互效果了。但是因为这个是 native 的特征,所以没法在第三方平台给去掉(比如:微信)。\n\n不过,我们可以通过前端的方式来降低这一效果被触发的几率。\n\n<img src=\"http://o4a7cbihz.qnssl.com/cover/0bf594ed-2bee-4df3-90f9-8de68787b1ba\" width = \"300\" alt=\"橡皮筋效果\" align=center />\n\n\n### 原理\n当页面的滚动条不在顶部或者底部的时候,就不会触发 “橡皮筋效果”。那我们就尽量让页面的滚动条在滑动的时候不在起始或末了位置进行 move 即可。\n\n### 源码\n\n```javascript\nvar preventViewScroll = function (classes) {\n var prevent = function (ele) {\n ele.addEventListener('touchstart', function () {\n var o = ele.scrollTop\n var i = ele.scrollHeight\n var t = o + ele.offsetHeight\n if (o === 0) { // 当滚动条在顶部的时候,强制置为1\n ele.scrollTop = 1\n } else if (t === i) { // 当滚动条在底部的时候,强制置为 o - 1\n ele.scrollTop = o - 1\n }\n })\n \n // 如果滚动条不在顶部,且不在底部,则不作处理;否则,阻止 move.\n ele.addEventListener('touchmove', function (e) {\n ele.offsetHeight < ele.scrollHeight && (e._isScroller = !0)\n })\n }\n\n var dom = document.querySelector(classes)\n dom && prevent(dom)\n\n document.body.addEventListener('touchmove', function (e) {\n e._isScroller || e.preventDefault()\n })\n}\n\n```\n\n### demo\n请在微信中打开: <http://lzwai.me/preventViewScroll/example.html>\n\n\n","source":"_posts/降低微信页面下拉露底(橡皮筋效果)的几率.md","raw":"---\ntitle: 降低微信页面下拉露底(橡皮筋效果)的几率\ndate: 2017-03-02\nauthor: 刘政\ncategory: frontend\ntags: preventViewScroll\n---\n\n当我们开发H5页面的时候,因为 webView 的原因,页面在滚动到页面顶部或底部的时候,容易触发全局的 “橡皮筋效果”。\n\n当页面内部还有滚动的时候,这个效果就会影响到 H5 自己的一些交互效果了。但是因为这个是 native 的特征,所以没法在第三方平台给去掉(比如:微信)。\n\n不过,我们可以通过前端的方式来降低这一效果被触发的几率。\n\n<img src=\"http://o4a7cbihz.qnssl.com/cover/0bf594ed-2bee-4df3-90f9-8de68787b1ba\" width = \"300\" alt=\"橡皮筋效果\" align=center />\n\n\n### 原理\n当页面的滚动条不在顶部或者底部的时候,就不会触发 “橡皮筋效果”。那我们就尽量让页面的滚动条在滑动的时候不在起始或末了位置进行 move 即可。\n\n### 源码\n\n```javascript\nvar preventViewScroll = function (classes) {\n var prevent = function (ele) {\n ele.addEventListener('touchstart', function () {\n var o = ele.scrollTop\n var i = ele.scrollHeight\n var t = o + ele.offsetHeight\n if (o === 0) { // 当滚动条在顶部的时候,强制置为1\n ele.scrollTop = 1\n } else if (t === i) { // 当滚动条在底部的时候,强制置为 o - 1\n ele.scrollTop = o - 1\n }\n })\n \n // 如果滚动条不在顶部,且不在底部,则不作处理;否则,阻止 move.\n ele.addEventListener('touchmove', function (e) {\n ele.offsetHeight < ele.scrollHeight && (e._isScroller = !0)\n })\n }\n\n var dom = document.querySelector(classes)\n dom && prevent(dom)\n\n document.body.addEventListener('touchmove', function (e) {\n e._isScroller || e.preventDefault()\n })\n}\n\n```\n\n### demo\n请在微信中打开: <http://lzwai.me/preventViewScroll/example.html>\n\n\n","slug":"降低微信页面下拉露底(橡皮筋效果)的几率","published":1,"updated":"2017-03-01T18:14:24.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab736000gas5fgpt2ej52","content":"<p>当我们开发H5页面的时候,因为 webView 的原因,页面在滚动到页面顶部或底部的时候,容易触发全局的 “橡皮筋效果”。</p>\n<p>当页面内部还有滚动的时候,这个效果就会影响到 H5 自己的一些交互效果了。但是因为这个是 native 的特征,所以没法在第三方平台给去掉(比如:微信)。</p>\n<p>不过,我们可以通过前端的方式来降低这一效果被触发的几率。</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/0bf594ed-2bee-4df3-90f9-8de68787b1ba\" width=\"300\" alt=\"橡皮筋效果\" align=\"center\"></p>\n<h3 id=\"原理\"><a href=\"#原理\" class=\"headerlink\" title=\"原理\"></a>原理</h3><p>当页面的滚动条不在顶部或者底部的时候,就不会触发 “橡皮筋效果”。那我们就尽量让页面的滚动条在滑动的时候不在起始或末了位置进行 move 即可。</p>\n<h3 id=\"源码\"><a href=\"#源码\" class=\"headerlink\" title=\"源码\"></a>源码</h3><figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> preventViewScroll = <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">classes</span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">var</span> prevent = <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">ele</span>) </span>{</div><div class=\"line\"> ele.addEventListener(<span class=\"string\">'touchstart'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">var</span> o = ele.scrollTop</div><div class=\"line\"> <span class=\"keyword\">var</span> i = ele.scrollHeight</div><div class=\"line\"> <span class=\"keyword\">var</span> t = o + ele.offsetHeight</div><div class=\"line\"> <span class=\"keyword\">if</span> (o === <span class=\"number\">0</span>) { <span class=\"comment\">// 当滚动条在顶部的时候,强制置为1</span></div><div class=\"line\"> ele.scrollTop = <span class=\"number\">1</span></div><div class=\"line\"> } <span class=\"keyword\">else</span> <span class=\"keyword\">if</span> (t === i) { <span class=\"comment\">// 当滚动条在底部的时候,强制置为 o - 1</span></div><div class=\"line\"> ele.scrollTop = o - <span class=\"number\">1</span></div><div class=\"line\"> }</div><div class=\"line\"> })</div><div class=\"line\"> </div><div class=\"line\"> <span class=\"comment\">// 如果滚动条不在顶部,且不在底部,则不作处理;否则,阻止 move.</span></div><div class=\"line\"> ele.addEventListener(<span class=\"string\">'touchmove'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">e</span>) </span>{</div><div class=\"line\"> ele.offsetHeight < ele.scrollHeight && (e._isScroller = !<span class=\"number\">0</span>)</div><div class=\"line\"> })</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> <span class=\"keyword\">var</span> dom = <span class=\"built_in\">document</span>.querySelector(classes)</div><div class=\"line\"> dom && prevent(dom)</div><div class=\"line\"></div><div class=\"line\"> <span class=\"built_in\">document</span>.body.addEventListener(<span class=\"string\">'touchmove'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">e</span>) </span>{</div><div class=\"line\"> e._isScroller || e.preventDefault()</div><div class=\"line\"> })</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<h3 id=\"demo\"><a href=\"#demo\" class=\"headerlink\" title=\"demo\"></a>demo</h3><p>请在微信中打开: <a href=\"http://lzwai.me/preventViewScroll/example.html\" target=\"_blank\" rel=\"external\">http://lzwai.me/preventViewScroll/example.html</a></p>\n","excerpt":"","more":"<p>当我们开发H5页面的时候,因为 webView 的原因,页面在滚动到页面顶部或底部的时候,容易触发全局的 “橡皮筋效果”。</p>\n<p>当页面内部还有滚动的时候,这个效果就会影响到 H5 自己的一些交互效果了。但是因为这个是 native 的特征,所以没法在第三方平台给去掉(比如:微信)。</p>\n<p>不过,我们可以通过前端的方式来降低这一效果被触发的几率。</p>\n<p><img src=\"http://o4a7cbihz.qnssl.com/cover/0bf594ed-2bee-4df3-90f9-8de68787b1ba\" width = \"300\" alt=\"橡皮筋效果\" align=center /></p>\n<h3 id=\"原理\"><a href=\"#原理\" class=\"headerlink\" title=\"原理\"></a>原理</h3><p>当页面的滚动条不在顶部或者底部的时候,就不会触发 “橡皮筋效果”。那我们就尽量让页面的滚动条在滑动的时候不在起始或末了位置进行 move 即可。</p>\n<h3 id=\"源码\"><a href=\"#源码\" class=\"headerlink\" title=\"源码\"></a>源码</h3><figure class=\"highlight javascript\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div></pre></td><td class=\"code\"><pre><div class=\"line\"><span class=\"keyword\">var</span> preventViewScroll = <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">classes</span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">var</span> prevent = <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">ele</span>) </span>{</div><div class=\"line\"> ele.addEventListener(<span class=\"string\">'touchstart'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\"></span>) </span>{</div><div class=\"line\"> <span class=\"keyword\">var</span> o = ele.scrollTop</div><div class=\"line\"> <span class=\"keyword\">var</span> i = ele.scrollHeight</div><div class=\"line\"> <span class=\"keyword\">var</span> t = o + ele.offsetHeight</div><div class=\"line\"> <span class=\"keyword\">if</span> (o === <span class=\"number\">0</span>) { <span class=\"comment\">// 当滚动条在顶部的时候,强制置为1</span></div><div class=\"line\"> ele.scrollTop = <span class=\"number\">1</span></div><div class=\"line\"> } <span class=\"keyword\">else</span> <span class=\"keyword\">if</span> (t === i) { <span class=\"comment\">// 当滚动条在底部的时候,强制置为 o - 1</span></div><div class=\"line\"> ele.scrollTop = o - <span class=\"number\">1</span></div><div class=\"line\"> }</div><div class=\"line\"> })</div><div class=\"line\"> </div><div class=\"line\"> <span class=\"comment\">// 如果滚动条不在顶部,且不在底部,则不作处理;否则,阻止 move.</span></div><div class=\"line\"> ele.addEventListener(<span class=\"string\">'touchmove'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">e</span>) </span>{</div><div class=\"line\"> ele.offsetHeight < ele.scrollHeight && (e._isScroller = !<span class=\"number\">0</span>)</div><div class=\"line\"> })</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> <span class=\"keyword\">var</span> dom = <span class=\"built_in\">document</span>.querySelector(classes)</div><div class=\"line\"> dom && prevent(dom)</div><div class=\"line\"></div><div class=\"line\"> <span class=\"built_in\">document</span>.body.addEventListener(<span class=\"string\">'touchmove'</span>, <span class=\"function\"><span class=\"keyword\">function</span> (<span class=\"params\">e</span>) </span>{</div><div class=\"line\"> e._isScroller || e.preventDefault()</div><div class=\"line\"> })</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<h3 id=\"demo\"><a href=\"#demo\" class=\"headerlink\" title=\"demo\"></a>demo</h3><p>请在微信中打开: <a href=\"http://lzwai.me/preventViewScroll/example.html\">http://lzwai.me/preventViewScroll/example.html</a></p>\n"},{"title":"纵览 Cocoa 的 Model-View-Controller 模式","date":"2017-03-12T16:00:00.000Z","author":"史江凯","_content":"\n**文章版权归 2016 Matt Gallagher, <https://cocoawithlove.com>. 原英文版点这里:[Looking at Model-View-Controller in Cocoa](https://www.cocoawithlove.com/blog/mvc-and-cocoa.html). 本文的翻译和发布经过原作者同意。**\n\n# Looking at Model-View-Controller in Cocoa\n\n##### February 28, 2017 by Matt Gallagher\n\n##### Tags: cocoa, app design\n\n依照 Apple 文档,Cocoa 应用的标准模式称为 Model-View-Controller。别管名字,这个模式跟 Smalltalk-80 中 Model-View-Controller 的原始定义的就大不一样。Cocoa 应用的设计模式实际上与 Taligent(1990 年代 Apple 参与合作开发的项目)开发时的想法有更多共同点,而不是同期的 Smalltalk。 \n这篇文章我会回顾一点 Cocoa 开始时的应用设计模式背后的理论和历史,讨论 Model-View-Controller 的明显缺点,Apple 解决缺点的尝试,好奇下一次的大改进何时到来。\n\n```\nContents\n 1. Smalltalk-80\n 2. Cocoa (AppKit/UIKit)\n 3. Taligent\n 4. The Controller Problem\n 5. Bindings\n 6. Something new?\n 7. Conclusion\n```\n\n### Smalltalk-80\n\n> UI 开发中引用最多的模式可能就是 Model View Controller(MVC),也是错误引用最多的。我数不清有多少次,别人把一些别的当 MVC 描述给我,结果却一点都不像。 - Martin Fowler<sup>[1](#Fowler)</sup>, [GUI Architectures](https://www.martinfowler.com/eaaDev/uiArchs.html)\n\n我想快点指出上面的引用中 Martin Fowler 的意思,他用的定义,即 Smalltalk-80 起初用到的定义,Cocoa 开发的通用做法不是 Model-View-Controller。 \n在 Smalltalk-80 中,能交互的 views 被分离成了两个完全隔离的对象:View 对象和 Controller 对象。View 负责展示,但是任何点击或交互不由 View 处理,而是交给了 Controller。要理解的关键在于 Controller 既不加载,设置或管理 View,一个 Controller 也不处理多个 View 的 action。起初在 Model-View-Controller 的定义里,View 和 Controller 只是简单的对屏幕上单一控件的展示和动作处理。\n\n![](https://www.cocoawithlove.com/assets/blog/smalltalk_mvc.svg)\n\n*Smalltalk-80 版本的 Model-View-Controller*\n\nSmalltalk-80 的 Model-View-Controller 表明 model 是上面对象图中的中心节点,view 或 controller 与 model 的基本通信都很直接。<br>\n这种模式反映出 Smalltalk-80 是如何处理用户输入的,现代的程序很少会用到这种模式。从这个意义上来看,要么现代的框架不是真正的 Model-View-Controller,要么这个术语已经变成别的意思了。\n\n### Cocoa (AppKit/UIKit)\n\nCocoa 表示 Model-View-Controller 要尽可能的唤起程序设计中[呈现与内容相分离](https://en.wikipedia.org/wiki/Separation_of_presentation_and_content)这个概念(意思是 model 和 view 之间应该解耦,联系松散)。公平点儿说,不光 Cocoa 这么用,现在绝大多数情况下这个术语想表达的也跟起初 Smalltalk-80 的定义不一样了。 \n看一下 Cocoa 真正用的,Apple 的 Cocoa 指南里用到的 [Model-View-Controller](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) 定义看起来像下面这样:\n\n![](https://www.cocoawithlove.com/assets/blog/cocoa_mvc.svg)\n\n*Cocoa 版本的 Model-View-Controller*\n\n关键在于 controller 在对象图的中心,绝大多数通信靠 controller 传递,区别于 Smalltalk-80 版本的 model 在对象图的中心。\n\nCocoa 没有强迫 app 都用这个模式,但它隐含在所有的应用模版里。从 NIB 里加载文件就强烈鼓励使用 `NSWindowController` / `UIViewController`。`NSTableView` / `UITableView`的代理相关类意味着有一个协调类能够理解整个呈现 tableView 的责任。`UITabBarController` 和`UINavigationController`这样的类就明确要求 `UIViewController`能协调它们要滑进或划出的 view。\n\n### Taligent\n\n在学术讨论中,Cocoa 的 Model-View-Controller 通常被成为 Model-View-Presenter。这两个是相同的,除了 Cocoa 里叫 Controllers 而不是 Presenters。\"Presenter\" 通过设置场景,调度动作体现它们的角色。某些情况下,Presenter 可能称为 Supervising Controller,你可以把 Model-View-Supervising Controller 简单地理解成 Model-View-Controller。 \nModel-View-Presenter 起源于 [Taligent](https://en.wikipedia.org/wiki/Taligent) 项目。[MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java](http://www.wildcrest.com/Potel/Portfolio/mvp.pdf),这个引用最多的论文,成文于 1997 年。但[文档显示](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/classes/TGUIPresenter.html),至少早在 1995 年,Taligent 中的某些类就实现这个模式了。 \nTaligent 起初是苹果内部以\"pink\"为代号的项目,目的是为了开发一个操作系统以代替 [System 7](https://en.wikipedia.org/wiki/System_7)。 这个项目有一系列有名的开发、管理问题,和苹果同期撤掉的 [Copland](https://en.wikipedia.org/wiki/Copland_(operating_system)) 是难兄难弟。Taligent 后来得以在 IBM 延续,推出了 CommonPoint 应用框架,而不是一个操作系统,并于 1998 年被关掉。 \n\n*[This Wired article from 1993](https://www.wired.com/1993/02/taligent/) 给出了一个有意思的见解,Taligent 的明显膨胀和内讧导致了它的失败。*\n\n尽管 NeXTStep 早于 Taligent,AppKit 中的众多控制器类(定义在 AppKit 的 Model-View-Presenter 设计模式下的),直到 1996 年 NeXTStep 4 才出现( NeXTStep 的一个重要的重新设计版本,第一个保留`NS`前缀直今)。我不知道 NeXTStep 是否借鉴了 Taligent,可能这是持续发展的结果,也可能几家公司雇佣了相同团队里的成员。站在 2017 年,我能说的就是 Taligent 是第一个发行的。 \n\n*1995 年的 [Taligent documentation](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/index.html) 读起来令人着迷。[Guide to Designing Programs](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/WM/WM_3.html) 讨论了许多有关应用程序设计的想法。但是,[Programming with the Presentation Framework 指南](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/PF/PF_1.html) 却很烂:它令人困惑、过于技术化、难以实现。*\n\n### The Controller Problem\n\n理解 Cocoa 的 Model-View-Controller 作为 Model-View-Presenter 的变种(Presenter 或者 Supervising Controller,要负责其下所有 view 的生命周期,动作和变化的通知)很重要,因为它导致了这种模式里的最大问题:\"The Controller Problem\"。 \nThe Controller Problem,也叫做\"Massive/Huge/Giant View Controllers\",是指:Cocoa 里的 controllers 有着强烈变大的倾向,要负责和 view 相关的事情,虽然跟功能和数据的独立性无关。大多数大项目有许多超过 2000 行的 controller 类。 \n说明白点,问题不在于 controller 代码的多少,而是它们增大的方式:Cocoa 的控制器聚合了许多跟它们有关或无关的事情。一个控制器可能负责几个或多个 view,每个 view 有自己的结构,配置,数据展示,数据更新,布局,动画跟动作,和其他要交给父控制器维护的状态。 \n独立和依赖大规模得混合在一起是维护的噩梦。大量代码使得实际的依赖和无关的功能难以区分。 Controller 通常难以测试(由于 app 和 包状态管理难以分离),规模和半独立的结合使得问题更糟。 \nController Problem 的解决办法是持续地将大的 controller 重构成小的、简单的 controller。这可能需要重新设计、重新思考数据结构,以解开、排除 controller 间的依赖,并设计出在几个 controller 间通信的可行实现。这能做到,不过有许多工作量,也有风险:带来新的 bug,依然难以测试,除此之外,交给用户的也不会有新的功能。\n\n### Bindings\n\nApple 早就知道 Controller Problem 了,因为 Mac OS X 10.3 推出了 Cocoa Bindings。 \n绑定是两个组件间建立路径,通常是数据源和数据的观察者。绑定允许组件间不通过额外的代码实现交流。Cocoa bindings 里两个组件间建立的路径称为\"key-path\"。指明了 controller 至 model 的属性(管理 view 状态)的 key-path,绑定就能显著改善或消除 Controller Problem。\n\n![](https://www.cocoawithlove.com/assets/blog/cocoa_bindings_mvc.svg)\n\n*Code paths through the Controller are replaced by Bindings*\n\n推出十多年后,Cocoa Bindings 都被人忘了。AppKit 你仍然可以用到,它们也没被废弃。不过它们从没被引入到 UIKit,反映出在使界面编程更简单这点上,它们不够成功。 \n我认为 Bindings 能解决问题,某些情况下解决得挺好,特别是`NSArrayController`驱动一个`NSTableView`。我也能理解它们为什么没能征服 Mac 编程。 \nCocoa Bindings 的优势(试图控制器更少的代码)被 Interface Builder 的检查面板里的众多设置项做到了。可这令人有些困惑:代码里找不到,难以搜索(虽然 Xcode 也能搜索 XIB 文件了),很难 debug(数据变化没有堆栈跟踪),不容易教给新人(不愿意在检查面板里找),比起代码来更隐秘(XIB 里不能加注释),也会导致 Interface Builder 的本地化和版本控制合并的问题。 \n我觉得 Cocoa Bindings 的失败依然保留了添加自定义转换和自定义属性的困难。这些本来都能解决,但是注册转换和暴露 bindings 字典却令人厌倦。不用 bindings 在 controller 传数据更简单 ,也就是说 Bindings 想解决最简单的问题(本来也不用解决),却没有触及到更难的问题。\n\n### Something new?\n\n自 Cocoa Bindings 开始于 Mac OS X 10.3 以后,Apple 也没有再明确的尝试改变 Cocoa 的设计模式。 \nStoryboards 开始于 iOS 5 和 Mac OS X 10.10,不过 storyboards 也不是对设计模式的更换和改进。Storyboards 鼓励使用`NS`/`UIViewController`,加强了 Model-View-Presenter 模式。Storyboards 确实鼓励更小,更专注的 view controller,也减少了一点 \"Presentation\" 带来的页面设置和过渡的负担。但是,既然能通过 Interface Builder 配置,也就表现出了一系列 Cocoa Bindings 想解决的问题。 \n对想要设计模式更给力的某些人来说,Storyboards 没有提供新东西。 \n现在确实有些应用设计的新想法。Apple 之外,有 [Reactive Programming](https://www.cocoawithlove.com/blog/reactive-programming-what-and-why.html)(能实现 Bindings 的大部分功能),[Model-View-ViewModel](https://www.objc.io/issues/13-architecture/mvvm/)(把 controller 减少的功能转移到了跟 view 接近的 model 上),[unidirectional dataflow](http://reswift.github.io/ReSwift/master/)(减少 binding 需求,在整个 app 间广播所有的数据变化),这些在不同圈子都广受欢迎。 \n有些框架做得完全不一样,像 [React Native](https://facebook.github.io/react-native/) 或 [Swift-Elm](https://github.com/salutis/swift-elm),舍弃了 Swift 或 Cocoa,也带来了无法忽视的不足。\n这些能否影响到官方的 Cocoa app 开发,暂时还不清晰。Swift 表明 Apple 偶尔想改些东西,也有争论说 Swift 增加了设计模式的需求或利用 Swift 语言优势的 view 框架。不过,苹果开发一个仅 Swift 的框架,还需要时间。\n\n### Conclusion\n\n如果我们以 NeXTStep 4 作为 Cocoa 现在 Model-View-Controller 模式的开端,那到现在就 20 年了。它还可用,虽然有它的缺点,也不像曾经那么令人激动或高效了。 \nApple 很早就尝试过 Cocoa Bindings,这唯一的对设计模式的改进了。人们对它的接纳程度不一,它也不会带至 Apple 的新平台了。 \n我没有任何 AppKit 或 UIKit 团队的内部尝试的消息,未来 Apple 也不是随时都想做一些激动人心的改动。许多第三方框架想改进 Cocoa 整个设计模式,但还没有哪个能达成一致。我觉得这些努力反映出了人们对某些改进上的关注。\n\n***\n\n<span id=\"Fowler\">马丁·福勒,软件工程师,也是一个软件开发方面的著作者和国际知名演说家,专注于面向对象分析与设计,统一建模语言,领域建模,以及敏捷软件开发方法,包括极限编程。《重构---改善既有代码的设计》的作者,ThoughtWorks 的首席科学家。来自[维基百科](https://zh.wikipedia.org/wiki/%E9%A9%AC%E4%B8%81%C2%B7%E7%A6%8F%E5%8B%92)。</span>\n\n","source":"_posts/纵览 Cocoa 的 Model-View-Controller 模式.md","raw":"---\ntitle: 纵览 Cocoa 的 Model-View-Controller 模式\ndate: 2017-03-13\nauthor: 史江凯\ncategory: ios\ntags: Cocoa\n---\n\n**文章版权归 2016 Matt Gallagher, <https://cocoawithlove.com>. 原英文版点这里:[Looking at Model-View-Controller in Cocoa](https://www.cocoawithlove.com/blog/mvc-and-cocoa.html). 本文的翻译和发布经过原作者同意。**\n\n# Looking at Model-View-Controller in Cocoa\n\n##### February 28, 2017 by Matt Gallagher\n\n##### Tags: cocoa, app design\n\n依照 Apple 文档,Cocoa 应用的标准模式称为 Model-View-Controller。别管名字,这个模式跟 Smalltalk-80 中 Model-View-Controller 的原始定义的就大不一样。Cocoa 应用的设计模式实际上与 Taligent(1990 年代 Apple 参与合作开发的项目)开发时的想法有更多共同点,而不是同期的 Smalltalk。 \n这篇文章我会回顾一点 Cocoa 开始时的应用设计模式背后的理论和历史,讨论 Model-View-Controller 的明显缺点,Apple 解决缺点的尝试,好奇下一次的大改进何时到来。\n\n```\nContents\n 1. Smalltalk-80\n 2. Cocoa (AppKit/UIKit)\n 3. Taligent\n 4. The Controller Problem\n 5. Bindings\n 6. Something new?\n 7. Conclusion\n```\n\n### Smalltalk-80\n\n> UI 开发中引用最多的模式可能就是 Model View Controller(MVC),也是错误引用最多的。我数不清有多少次,别人把一些别的当 MVC 描述给我,结果却一点都不像。 - Martin Fowler<sup>[1](#Fowler)</sup>, [GUI Architectures](https://www.martinfowler.com/eaaDev/uiArchs.html)\n\n我想快点指出上面的引用中 Martin Fowler 的意思,他用的定义,即 Smalltalk-80 起初用到的定义,Cocoa 开发的通用做法不是 Model-View-Controller。 \n在 Smalltalk-80 中,能交互的 views 被分离成了两个完全隔离的对象:View 对象和 Controller 对象。View 负责展示,但是任何点击或交互不由 View 处理,而是交给了 Controller。要理解的关键在于 Controller 既不加载,设置或管理 View,一个 Controller 也不处理多个 View 的 action。起初在 Model-View-Controller 的定义里,View 和 Controller 只是简单的对屏幕上单一控件的展示和动作处理。\n\n![](https://www.cocoawithlove.com/assets/blog/smalltalk_mvc.svg)\n\n*Smalltalk-80 版本的 Model-View-Controller*\n\nSmalltalk-80 的 Model-View-Controller 表明 model 是上面对象图中的中心节点,view 或 controller 与 model 的基本通信都很直接。<br>\n这种模式反映出 Smalltalk-80 是如何处理用户输入的,现代的程序很少会用到这种模式。从这个意义上来看,要么现代的框架不是真正的 Model-View-Controller,要么这个术语已经变成别的意思了。\n\n### Cocoa (AppKit/UIKit)\n\nCocoa 表示 Model-View-Controller 要尽可能的唤起程序设计中[呈现与内容相分离](https://en.wikipedia.org/wiki/Separation_of_presentation_and_content)这个概念(意思是 model 和 view 之间应该解耦,联系松散)。公平点儿说,不光 Cocoa 这么用,现在绝大多数情况下这个术语想表达的也跟起初 Smalltalk-80 的定义不一样了。 \n看一下 Cocoa 真正用的,Apple 的 Cocoa 指南里用到的 [Model-View-Controller](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html) 定义看起来像下面这样:\n\n![](https://www.cocoawithlove.com/assets/blog/cocoa_mvc.svg)\n\n*Cocoa 版本的 Model-View-Controller*\n\n关键在于 controller 在对象图的中心,绝大多数通信靠 controller 传递,区别于 Smalltalk-80 版本的 model 在对象图的中心。\n\nCocoa 没有强迫 app 都用这个模式,但它隐含在所有的应用模版里。从 NIB 里加载文件就强烈鼓励使用 `NSWindowController` / `UIViewController`。`NSTableView` / `UITableView`的代理相关类意味着有一个协调类能够理解整个呈现 tableView 的责任。`UITabBarController` 和`UINavigationController`这样的类就明确要求 `UIViewController`能协调它们要滑进或划出的 view。\n\n### Taligent\n\n在学术讨论中,Cocoa 的 Model-View-Controller 通常被成为 Model-View-Presenter。这两个是相同的,除了 Cocoa 里叫 Controllers 而不是 Presenters。\"Presenter\" 通过设置场景,调度动作体现它们的角色。某些情况下,Presenter 可能称为 Supervising Controller,你可以把 Model-View-Supervising Controller 简单地理解成 Model-View-Controller。 \nModel-View-Presenter 起源于 [Taligent](https://en.wikipedia.org/wiki/Taligent) 项目。[MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java](http://www.wildcrest.com/Potel/Portfolio/mvp.pdf),这个引用最多的论文,成文于 1997 年。但[文档显示](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/classes/TGUIPresenter.html),至少早在 1995 年,Taligent 中的某些类就实现这个模式了。 \nTaligent 起初是苹果内部以\"pink\"为代号的项目,目的是为了开发一个操作系统以代替 [System 7](https://en.wikipedia.org/wiki/System_7)。 这个项目有一系列有名的开发、管理问题,和苹果同期撤掉的 [Copland](https://en.wikipedia.org/wiki/Copland_(operating_system)) 是难兄难弟。Taligent 后来得以在 IBM 延续,推出了 CommonPoint 应用框架,而不是一个操作系统,并于 1998 年被关掉。 \n\n*[This Wired article from 1993](https://www.wired.com/1993/02/taligent/) 给出了一个有意思的见解,Taligent 的明显膨胀和内讧导致了它的失败。*\n\n尽管 NeXTStep 早于 Taligent,AppKit 中的众多控制器类(定义在 AppKit 的 Model-View-Presenter 设计模式下的),直到 1996 年 NeXTStep 4 才出现( NeXTStep 的一个重要的重新设计版本,第一个保留`NS`前缀直今)。我不知道 NeXTStep 是否借鉴了 Taligent,可能这是持续发展的结果,也可能几家公司雇佣了相同团队里的成员。站在 2017 年,我能说的就是 Taligent 是第一个发行的。 \n\n*1995 年的 [Taligent documentation](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/index.html) 读起来令人着迷。[Guide to Designing Programs](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/WM/WM_3.html) 讨论了许多有关应用程序设计的想法。但是,[Programming with the Presentation Framework 指南](https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/PF/PF_1.html) 却很烂:它令人困惑、过于技术化、难以实现。*\n\n### The Controller Problem\n\n理解 Cocoa 的 Model-View-Controller 作为 Model-View-Presenter 的变种(Presenter 或者 Supervising Controller,要负责其下所有 view 的生命周期,动作和变化的通知)很重要,因为它导致了这种模式里的最大问题:\"The Controller Problem\"。 \nThe Controller Problem,也叫做\"Massive/Huge/Giant View Controllers\",是指:Cocoa 里的 controllers 有着强烈变大的倾向,要负责和 view 相关的事情,虽然跟功能和数据的独立性无关。大多数大项目有许多超过 2000 行的 controller 类。 \n说明白点,问题不在于 controller 代码的多少,而是它们增大的方式:Cocoa 的控制器聚合了许多跟它们有关或无关的事情。一个控制器可能负责几个或多个 view,每个 view 有自己的结构,配置,数据展示,数据更新,布局,动画跟动作,和其他要交给父控制器维护的状态。 \n独立和依赖大规模得混合在一起是维护的噩梦。大量代码使得实际的依赖和无关的功能难以区分。 Controller 通常难以测试(由于 app 和 包状态管理难以分离),规模和半独立的结合使得问题更糟。 \nController Problem 的解决办法是持续地将大的 controller 重构成小的、简单的 controller。这可能需要重新设计、重新思考数据结构,以解开、排除 controller 间的依赖,并设计出在几个 controller 间通信的可行实现。这能做到,不过有许多工作量,也有风险:带来新的 bug,依然难以测试,除此之外,交给用户的也不会有新的功能。\n\n### Bindings\n\nApple 早就知道 Controller Problem 了,因为 Mac OS X 10.3 推出了 Cocoa Bindings。 \n绑定是两个组件间建立路径,通常是数据源和数据的观察者。绑定允许组件间不通过额外的代码实现交流。Cocoa bindings 里两个组件间建立的路径称为\"key-path\"。指明了 controller 至 model 的属性(管理 view 状态)的 key-path,绑定就能显著改善或消除 Controller Problem。\n\n![](https://www.cocoawithlove.com/assets/blog/cocoa_bindings_mvc.svg)\n\n*Code paths through the Controller are replaced by Bindings*\n\n推出十多年后,Cocoa Bindings 都被人忘了。AppKit 你仍然可以用到,它们也没被废弃。不过它们从没被引入到 UIKit,反映出在使界面编程更简单这点上,它们不够成功。 \n我认为 Bindings 能解决问题,某些情况下解决得挺好,特别是`NSArrayController`驱动一个`NSTableView`。我也能理解它们为什么没能征服 Mac 编程。 \nCocoa Bindings 的优势(试图控制器更少的代码)被 Interface Builder 的检查面板里的众多设置项做到了。可这令人有些困惑:代码里找不到,难以搜索(虽然 Xcode 也能搜索 XIB 文件了),很难 debug(数据变化没有堆栈跟踪),不容易教给新人(不愿意在检查面板里找),比起代码来更隐秘(XIB 里不能加注释),也会导致 Interface Builder 的本地化和版本控制合并的问题。 \n我觉得 Cocoa Bindings 的失败依然保留了添加自定义转换和自定义属性的困难。这些本来都能解决,但是注册转换和暴露 bindings 字典却令人厌倦。不用 bindings 在 controller 传数据更简单 ,也就是说 Bindings 想解决最简单的问题(本来也不用解决),却没有触及到更难的问题。\n\n### Something new?\n\n自 Cocoa Bindings 开始于 Mac OS X 10.3 以后,Apple 也没有再明确的尝试改变 Cocoa 的设计模式。 \nStoryboards 开始于 iOS 5 和 Mac OS X 10.10,不过 storyboards 也不是对设计模式的更换和改进。Storyboards 鼓励使用`NS`/`UIViewController`,加强了 Model-View-Presenter 模式。Storyboards 确实鼓励更小,更专注的 view controller,也减少了一点 \"Presentation\" 带来的页面设置和过渡的负担。但是,既然能通过 Interface Builder 配置,也就表现出了一系列 Cocoa Bindings 想解决的问题。 \n对想要设计模式更给力的某些人来说,Storyboards 没有提供新东西。 \n现在确实有些应用设计的新想法。Apple 之外,有 [Reactive Programming](https://www.cocoawithlove.com/blog/reactive-programming-what-and-why.html)(能实现 Bindings 的大部分功能),[Model-View-ViewModel](https://www.objc.io/issues/13-architecture/mvvm/)(把 controller 减少的功能转移到了跟 view 接近的 model 上),[unidirectional dataflow](http://reswift.github.io/ReSwift/master/)(减少 binding 需求,在整个 app 间广播所有的数据变化),这些在不同圈子都广受欢迎。 \n有些框架做得完全不一样,像 [React Native](https://facebook.github.io/react-native/) 或 [Swift-Elm](https://github.com/salutis/swift-elm),舍弃了 Swift 或 Cocoa,也带来了无法忽视的不足。\n这些能否影响到官方的 Cocoa app 开发,暂时还不清晰。Swift 表明 Apple 偶尔想改些东西,也有争论说 Swift 增加了设计模式的需求或利用 Swift 语言优势的 view 框架。不过,苹果开发一个仅 Swift 的框架,还需要时间。\n\n### Conclusion\n\n如果我们以 NeXTStep 4 作为 Cocoa 现在 Model-View-Controller 模式的开端,那到现在就 20 年了。它还可用,虽然有它的缺点,也不像曾经那么令人激动或高效了。 \nApple 很早就尝试过 Cocoa Bindings,这唯一的对设计模式的改进了。人们对它的接纳程度不一,它也不会带至 Apple 的新平台了。 \n我没有任何 AppKit 或 UIKit 团队的内部尝试的消息,未来 Apple 也不是随时都想做一些激动人心的改动。许多第三方框架想改进 Cocoa 整个设计模式,但还没有哪个能达成一致。我觉得这些努力反映出了人们对某些改进上的关注。\n\n***\n\n<span id=\"Fowler\">马丁·福勒,软件工程师,也是一个软件开发方面的著作者和国际知名演说家,专注于面向对象分析与设计,统一建模语言,领域建模,以及敏捷软件开发方法,包括极限编程。《重构---改善既有代码的设计》的作者,ThoughtWorks 的首席科学家。来自[维基百科](https://zh.wikipedia.org/wiki/%E9%A9%AC%E4%B8%81%C2%B7%E7%A6%8F%E5%8B%92)。</span>\n\n","slug":"纵览 Cocoa 的 Model-View-Controller 模式","published":1,"updated":"2017-03-13T03:26:34.000Z","comments":1,"layout":"post","photos":[],"link":"","_id":"cj17ab738000jas5fhjkvi84h","content":"<p><strong>文章版权归 2016 Matt Gallagher, <a href=\"https://cocoawithlove.com\" target=\"_blank\" rel=\"external\">https://cocoawithlove.com</a>. 原英文版点这里:<a href=\"https://www.cocoawithlove.com/blog/mvc-and-cocoa.html\" target=\"_blank\" rel=\"external\">Looking at Model-View-Controller in Cocoa</a>. 本文的翻译和发布经过原作者同意。</strong></p>\n<h1 id=\"Looking-at-Model-View-Controller-in-Cocoa\"><a href=\"#Looking-at-Model-View-Controller-in-Cocoa\" class=\"headerlink\" title=\"Looking at Model-View-Controller in Cocoa\"></a>Looking at Model-View-Controller in Cocoa</h1><h5 id=\"February-28-2017-by-Matt-Gallagher\"><a href=\"#February-28-2017-by-Matt-Gallagher\" class=\"headerlink\" title=\"February 28, 2017 by Matt Gallagher\"></a>February 28, 2017 by Matt Gallagher</h5><h5 id=\"Tags-cocoa-app-design\"><a href=\"#Tags-cocoa-app-design\" class=\"headerlink\" title=\"Tags: cocoa, app design\"></a>Tags: cocoa, app design</h5><p>依照 Apple 文档,Cocoa 应用的标准模式称为 Model-View-Controller。别管名字,这个模式跟 Smalltalk-80 中 Model-View-Controller 的原始定义的就大不一样。Cocoa 应用的设计模式实际上与 Taligent(1990 年代 Apple 参与合作开发的项目)开发时的想法有更多共同点,而不是同期的 Smalltalk。<br>这篇文章我会回顾一点 Cocoa 开始时的应用设计模式背后的理论和历史,讨论 Model-View-Controller 的明显缺点,Apple 解决缺点的尝试,好奇下一次的大改进何时到来。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div></pre></td><td class=\"code\"><pre><div class=\"line\">Contents</div><div class=\"line\"> 1. Smalltalk-80</div><div class=\"line\"> 2. Cocoa (AppKit/UIKit)</div><div class=\"line\"> 3. Taligent</div><div class=\"line\"> 4. The Controller Problem</div><div class=\"line\"> 5. Bindings</div><div class=\"line\"> 6. Something new?</div><div class=\"line\"> 7. Conclusion</div></pre></td></tr></table></figure>\n<h3 id=\"Smalltalk-80\"><a href=\"#Smalltalk-80\" class=\"headerlink\" title=\"Smalltalk-80\"></a>Smalltalk-80</h3><blockquote>\n<p>UI 开发中引用最多的模式可能就是 Model View Controller(MVC),也是错误引用最多的。我数不清有多少次,别人把一些别的当 MVC 描述给我,结果却一点都不像。 - Martin Fowler<sup><a href=\"#Fowler\">1</a></sup>, <a href=\"https://www.martinfowler.com/eaaDev/uiArchs.html\" target=\"_blank\" rel=\"external\">GUI Architectures</a></p>\n</blockquote>\n<p>我想快点指出上面的引用中 Martin Fowler 的意思,他用的定义,即 Smalltalk-80 起初用到的定义,Cocoa 开发的通用做法不是 Model-View-Controller。<br>在 Smalltalk-80 中,能交互的 views 被分离成了两个完全隔离的对象:View 对象和 Controller 对象。View 负责展示,但是任何点击或交互不由 View 处理,而是交给了 Controller。要理解的关键在于 Controller 既不加载,设置或管理 View,一个 Controller 也不处理多个 View 的 action。起初在 Model-View-Controller 的定义里,View 和 Controller 只是简单的对屏幕上单一控件的展示和动作处理。</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/smalltalk_mvc.svg\" alt=\"\"></p>\n<p><em>Smalltalk-80 版本的 Model-View-Controller</em></p>\n<p>Smalltalk-80 的 Model-View-Controller 表明 model 是上面对象图中的中心节点,view 或 controller 与 model 的基本通信都很直接。<br><br>这种模式反映出 Smalltalk-80 是如何处理用户输入的,现代的程序很少会用到这种模式。从这个意义上来看,要么现代的框架不是真正的 Model-View-Controller,要么这个术语已经变成别的意思了。</p>\n<h3 id=\"Cocoa-AppKit-UIKit\"><a href=\"#Cocoa-AppKit-UIKit\" class=\"headerlink\" title=\"Cocoa (AppKit/UIKit)\"></a>Cocoa (AppKit/UIKit)</h3><p>Cocoa 表示 Model-View-Controller 要尽可能的唤起程序设计中<a href=\"https://en.wikipedia.org/wiki/Separation_of_presentation_and_content\" target=\"_blank\" rel=\"external\">呈现与内容相分离</a>这个概念(意思是 model 和 view 之间应该解耦,联系松散)。公平点儿说,不光 Cocoa 这么用,现在绝大多数情况下这个术语想表达的也跟起初 Smalltalk-80 的定义不一样了。<br>看一下 Cocoa 真正用的,Apple 的 Cocoa 指南里用到的 <a href=\"https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html\" target=\"_blank\" rel=\"external\">Model-View-Controller</a> 定义看起来像下面这样:</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/cocoa_mvc.svg\" alt=\"\"></p>\n<p><em>Cocoa 版本的 Model-View-Controller</em></p>\n<p>关键在于 controller 在对象图的中心,绝大多数通信靠 controller 传递,区别于 Smalltalk-80 版本的 model 在对象图的中心。</p>\n<p>Cocoa 没有强迫 app 都用这个模式,但它隐含在所有的应用模版里。从 NIB 里加载文件就强烈鼓励使用 <code>NSWindowController</code> / <code>UIViewController</code>。<code>NSTableView</code> / <code>UITableView</code>的代理相关类意味着有一个协调类能够理解整个呈现 tableView 的责任。<code>UITabBarController</code> 和<code>UINavigationController</code>这样的类就明确要求 <code>UIViewController</code>能协调它们要滑进或划出的 view。</p>\n<h3 id=\"Taligent\"><a href=\"#Taligent\" class=\"headerlink\" title=\"Taligent\"></a>Taligent</h3><p>在学术讨论中,Cocoa 的 Model-View-Controller 通常被成为 Model-View-Presenter。这两个是相同的,除了 Cocoa 里叫 Controllers 而不是 Presenters。”Presenter” 通过设置场景,调度动作体现它们的角色。某些情况下,Presenter 可能称为 Supervising Controller,你可以把 Model-View-Supervising Controller 简单地理解成 Model-View-Controller。<br>Model-View-Presenter 起源于 <a href=\"https://en.wikipedia.org/wiki/Taligent\" target=\"_blank\" rel=\"external\">Taligent</a> 项目。<a href=\"http://www.wildcrest.com/Potel/Portfolio/mvp.pdf\" target=\"_blank\" rel=\"external\">MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java</a>,这个引用最多的论文,成文于 1997 年。但<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/classes/TGUIPresenter.html\" target=\"_blank\" rel=\"external\">文档显示</a>,至少早在 1995 年,Taligent 中的某些类就实现这个模式了。<br>Taligent 起初是苹果内部以”pink”为代号的项目,目的是为了开发一个操作系统以代替 <a href=\"https://en.wikipedia.org/wiki/System_7\" target=\"_blank\" rel=\"external\">System 7</a>。 这个项目有一系列有名的开发、管理问题,和苹果同期撤掉的 <a href=\"https://en.wikipedia.org/wiki/Copland_(operating_system\" target=\"_blank\" rel=\"external\">Copland</a>) 是难兄难弟。Taligent 后来得以在 IBM 延续,推出了 CommonPoint 应用框架,而不是一个操作系统,并于 1998 年被关掉。 </p>\n<p><em><a href=\"https://www.wired.com/1993/02/taligent/\" target=\"_blank\" rel=\"external\">This Wired article from 1993</a> 给出了一个有意思的见解,Taligent 的明显膨胀和内讧导致了它的失败。</em></p>\n<p>尽管 NeXTStep 早于 Taligent,AppKit 中的众多控制器类(定义在 AppKit 的 Model-View-Presenter 设计模式下的),直到 1996 年 NeXTStep 4 才出现( NeXTStep 的一个重要的重新设计版本,第一个保留<code>NS</code>前缀直今)。我不知道 NeXTStep 是否借鉴了 Taligent,可能这是持续发展的结果,也可能几家公司雇佣了相同团队里的成员。站在 2017 年,我能说的就是 Taligent 是第一个发行的。 </p>\n<p><em>1995 年的 <a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/index.html\" target=\"_blank\" rel=\"external\">Taligent documentation</a> 读起来令人着迷。<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/WM/WM_3.html\" target=\"_blank\" rel=\"external\">Guide to Designing Programs</a> 讨论了许多有关应用程序设计的想法。但是,<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/PF/PF_1.html\" target=\"_blank\" rel=\"external\">Programming with the Presentation Framework 指南</a> 却很烂:它令人困惑、过于技术化、难以实现。</em></p>\n<h3 id=\"The-Controller-Problem\"><a href=\"#The-Controller-Problem\" class=\"headerlink\" title=\"The Controller Problem\"></a>The Controller Problem</h3><p>理解 Cocoa 的 Model-View-Controller 作为 Model-View-Presenter 的变种(Presenter 或者 Supervising Controller,要负责其下所有 view 的生命周期,动作和变化的通知)很重要,因为它导致了这种模式里的最大问题:”The Controller Problem”。<br>The Controller Problem,也叫做”Massive/Huge/Giant View Controllers”,是指:Cocoa 里的 controllers 有着强烈变大的倾向,要负责和 view 相关的事情,虽然跟功能和数据的独立性无关。大多数大项目有许多超过 2000 行的 controller 类。<br>说明白点,问题不在于 controller 代码的多少,而是它们增大的方式:Cocoa 的控制器聚合了许多跟它们有关或无关的事情。一个控制器可能负责几个或多个 view,每个 view 有自己的结构,配置,数据展示,数据更新,布局,动画跟动作,和其他要交给父控制器维护的状态。<br>独立和依赖大规模得混合在一起是维护的噩梦。大量代码使得实际的依赖和无关的功能难以区分。 Controller 通常难以测试(由于 app 和 包状态管理难以分离),规模和半独立的结合使得问题更糟。<br>Controller Problem 的解决办法是持续地将大的 controller 重构成小的、简单的 controller。这可能需要重新设计、重新思考数据结构,以解开、排除 controller 间的依赖,并设计出在几个 controller 间通信的可行实现。这能做到,不过有许多工作量,也有风险:带来新的 bug,依然难以测试,除此之外,交给用户的也不会有新的功能。</p>\n<h3 id=\"Bindings\"><a href=\"#Bindings\" class=\"headerlink\" title=\"Bindings\"></a>Bindings</h3><p>Apple 早就知道 Controller Problem 了,因为 Mac OS X 10.3 推出了 Cocoa Bindings。<br>绑定是两个组件间建立路径,通常是数据源和数据的观察者。绑定允许组件间不通过额外的代码实现交流。Cocoa bindings 里两个组件间建立的路径称为”key-path”。指明了 controller 至 model 的属性(管理 view 状态)的 key-path,绑定就能显著改善或消除 Controller Problem。</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/cocoa_bindings_mvc.svg\" alt=\"\"></p>\n<p><em>Code paths through the Controller are replaced by Bindings</em></p>\n<p>推出十多年后,Cocoa Bindings 都被人忘了。AppKit 你仍然可以用到,它们也没被废弃。不过它们从没被引入到 UIKit,反映出在使界面编程更简单这点上,它们不够成功。<br>我认为 Bindings 能解决问题,某些情况下解决得挺好,特别是<code>NSArrayController</code>驱动一个<code>NSTableView</code>。我也能理解它们为什么没能征服 Mac 编程。<br>Cocoa Bindings 的优势(试图控制器更少的代码)被 Interface Builder 的检查面板里的众多设置项做到了。可这令人有些困惑:代码里找不到,难以搜索(虽然 Xcode 也能搜索 XIB 文件了),很难 debug(数据变化没有堆栈跟踪),不容易教给新人(不愿意在检查面板里找),比起代码来更隐秘(XIB 里不能加注释),也会导致 Interface Builder 的本地化和版本控制合并的问题。<br>我觉得 Cocoa Bindings 的失败依然保留了添加自定义转换和自定义属性的困难。这些本来都能解决,但是注册转换和暴露 bindings 字典却令人厌倦。不用 bindings 在 controller 传数据更简单 ,也就是说 Bindings 想解决最简单的问题(本来也不用解决),却没有触及到更难的问题。</p>\n<h3 id=\"Something-new\"><a href=\"#Something-new\" class=\"headerlink\" title=\"Something new?\"></a>Something new?</h3><p>自 Cocoa Bindings 开始于 Mac OS X 10.3 以后,Apple 也没有再明确的尝试改变 Cocoa 的设计模式。<br>Storyboards 开始于 iOS 5 和 Mac OS X 10.10,不过 storyboards 也不是对设计模式的更换和改进。Storyboards 鼓励使用<code>NS</code>/<code>UIViewController</code>,加强了 Model-View-Presenter 模式。Storyboards 确实鼓励更小,更专注的 view controller,也减少了一点 “Presentation” 带来的页面设置和过渡的负担。但是,既然能通过 Interface Builder 配置,也就表现出了一系列 Cocoa Bindings 想解决的问题。<br>对想要设计模式更给力的某些人来说,Storyboards 没有提供新东西。<br>现在确实有些应用设计的新想法。Apple 之外,有 <a href=\"https://www.cocoawithlove.com/blog/reactive-programming-what-and-why.html\" target=\"_blank\" rel=\"external\">Reactive Programming</a>(能实现 Bindings 的大部分功能),<a href=\"https://www.objc.io/issues/13-architecture/mvvm/\" target=\"_blank\" rel=\"external\">Model-View-ViewModel</a>(把 controller 减少的功能转移到了跟 view 接近的 model 上),<a href=\"http://reswift.github.io/ReSwift/master/\" target=\"_blank\" rel=\"external\">unidirectional dataflow</a>(减少 binding 需求,在整个 app 间广播所有的数据变化),这些在不同圈子都广受欢迎。<br>有些框架做得完全不一样,像 <a href=\"https://facebook.github.io/react-native/\" target=\"_blank\" rel=\"external\">React Native</a> 或 <a href=\"https://github.com/salutis/swift-elm\" target=\"_blank\" rel=\"external\">Swift-Elm</a>,舍弃了 Swift 或 Cocoa,也带来了无法忽视的不足。<br>这些能否影响到官方的 Cocoa app 开发,暂时还不清晰。Swift 表明 Apple 偶尔想改些东西,也有争论说 Swift 增加了设计模式的需求或利用 Swift 语言优势的 view 框架。不过,苹果开发一个仅 Swift 的框架,还需要时间。</p>\n<h3 id=\"Conclusion\"><a href=\"#Conclusion\" class=\"headerlink\" title=\"Conclusion\"></a>Conclusion</h3><p>如果我们以 NeXTStep 4 作为 Cocoa 现在 Model-View-Controller 模式的开端,那到现在就 20 年了。它还可用,虽然有它的缺点,也不像曾经那么令人激动或高效了。<br>Apple 很早就尝试过 Cocoa Bindings,这唯一的对设计模式的改进了。人们对它的接纳程度不一,它也不会带至 Apple 的新平台了。<br>我没有任何 AppKit 或 UIKit 团队的内部尝试的消息,未来 Apple 也不是随时都想做一些激动人心的改动。许多第三方框架想改进 Cocoa 整个设计模式,但还没有哪个能达成一致。我觉得这些努力反映出了人们对某些改进上的关注。</p>\n<hr>\n<p><span id=\"Fowler\">马丁·福勒,软件工程师,也是一个软件开发方面的著作者和国际知名演说家,专注于面向对象分析与设计,统一建模语言,领域建模,以及敏捷软件开发方法,包括极限编程。《重构—改善既有代码的设计》的作者,ThoughtWorks 的首席科学家。来自<a href=\"https://zh.wikipedia.org/wiki/%E9%A9%AC%E4%B8%81%C2%B7%E7%A6%8F%E5%8B%92\" target=\"_blank\" rel=\"external\">维基百科</a>。</span></p>\n","excerpt":"","more":"<p><strong>文章版权归 2016 Matt Gallagher, <a href=\"https://cocoawithlove.com\">https://cocoawithlove.com</a>. 原英文版点这里:<a href=\"https://www.cocoawithlove.com/blog/mvc-and-cocoa.html\">Looking at Model-View-Controller in Cocoa</a>. 本文的翻译和发布经过原作者同意。</strong></p>\n<h1 id=\"Looking-at-Model-View-Controller-in-Cocoa\"><a href=\"#Looking-at-Model-View-Controller-in-Cocoa\" class=\"headerlink\" title=\"Looking at Model-View-Controller in Cocoa\"></a>Looking at Model-View-Controller in Cocoa</h1><h5 id=\"February-28-2017-by-Matt-Gallagher\"><a href=\"#February-28-2017-by-Matt-Gallagher\" class=\"headerlink\" title=\"February 28, 2017 by Matt Gallagher\"></a>February 28, 2017 by Matt Gallagher</h5><h5 id=\"Tags-cocoa-app-design\"><a href=\"#Tags-cocoa-app-design\" class=\"headerlink\" title=\"Tags: cocoa, app design\"></a>Tags: cocoa, app design</h5><p>依照 Apple 文档,Cocoa 应用的标准模式称为 Model-View-Controller。别管名字,这个模式跟 Smalltalk-80 中 Model-View-Controller 的原始定义的就大不一样。Cocoa 应用的设计模式实际上与 Taligent(1990 年代 Apple 参与合作开发的项目)开发时的想法有更多共同点,而不是同期的 Smalltalk。<br>这篇文章我会回顾一点 Cocoa 开始时的应用设计模式背后的理论和历史,讨论 Model-View-Controller 的明显缺点,Apple 解决缺点的尝试,好奇下一次的大改进何时到来。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div></pre></td><td class=\"code\"><pre><div class=\"line\">Contents</div><div class=\"line\"> 1. Smalltalk-80</div><div class=\"line\"> 2. Cocoa (AppKit/UIKit)</div><div class=\"line\"> 3. Taligent</div><div class=\"line\"> 4. The Controller Problem</div><div class=\"line\"> 5. Bindings</div><div class=\"line\"> 6. Something new?</div><div class=\"line\"> 7. Conclusion</div></pre></td></tr></table></figure>\n<h3 id=\"Smalltalk-80\"><a href=\"#Smalltalk-80\" class=\"headerlink\" title=\"Smalltalk-80\"></a>Smalltalk-80</h3><blockquote>\n<p>UI 开发中引用最多的模式可能就是 Model View Controller(MVC),也是错误引用最多的。我数不清有多少次,别人把一些别的当 MVC 描述给我,结果却一点都不像。 - Martin Fowler<sup><a href=\"#Fowler\">1</a></sup>, <a href=\"https://www.martinfowler.com/eaaDev/uiArchs.html\">GUI Architectures</a></p>\n</blockquote>\n<p>我想快点指出上面的引用中 Martin Fowler 的意思,他用的定义,即 Smalltalk-80 起初用到的定义,Cocoa 开发的通用做法不是 Model-View-Controller。<br>在 Smalltalk-80 中,能交互的 views 被分离成了两个完全隔离的对象:View 对象和 Controller 对象。View 负责展示,但是任何点击或交互不由 View 处理,而是交给了 Controller。要理解的关键在于 Controller 既不加载,设置或管理 View,一个 Controller 也不处理多个 View 的 action。起初在 Model-View-Controller 的定义里,View 和 Controller 只是简单的对屏幕上单一控件的展示和动作处理。</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/smalltalk_mvc.svg\" alt=\"\"></p>\n<p><em>Smalltalk-80 版本的 Model-View-Controller</em></p>\n<p>Smalltalk-80 的 Model-View-Controller 表明 model 是上面对象图中的中心节点,view 或 controller 与 model 的基本通信都很直接。<br><br>这种模式反映出 Smalltalk-80 是如何处理用户输入的,现代的程序很少会用到这种模式。从这个意义上来看,要么现代的框架不是真正的 Model-View-Controller,要么这个术语已经变成别的意思了。</p>\n<h3 id=\"Cocoa-AppKit-UIKit\"><a href=\"#Cocoa-AppKit-UIKit\" class=\"headerlink\" title=\"Cocoa (AppKit/UIKit)\"></a>Cocoa (AppKit/UIKit)</h3><p>Cocoa 表示 Model-View-Controller 要尽可能的唤起程序设计中<a href=\"https://en.wikipedia.org/wiki/Separation_of_presentation_and_content\">呈现与内容相分离</a>这个概念(意思是 model 和 view 之间应该解耦,联系松散)。公平点儿说,不光 Cocoa 这么用,现在绝大多数情况下这个术语想表达的也跟起初 Smalltalk-80 的定义不一样了。<br>看一下 Cocoa 真正用的,Apple 的 Cocoa 指南里用到的 <a href=\"https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html\">Model-View-Controller</a> 定义看起来像下面这样:</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/cocoa_mvc.svg\" alt=\"\"></p>\n<p><em>Cocoa 版本的 Model-View-Controller</em></p>\n<p>关键在于 controller 在对象图的中心,绝大多数通信靠 controller 传递,区别于 Smalltalk-80 版本的 model 在对象图的中心。</p>\n<p>Cocoa 没有强迫 app 都用这个模式,但它隐含在所有的应用模版里。从 NIB 里加载文件就强烈鼓励使用 <code>NSWindowController</code> / <code>UIViewController</code>。<code>NSTableView</code> / <code>UITableView</code>的代理相关类意味着有一个协调类能够理解整个呈现 tableView 的责任。<code>UITabBarController</code> 和<code>UINavigationController</code>这样的类就明确要求 <code>UIViewController</code>能协调它们要滑进或划出的 view。</p>\n<h3 id=\"Taligent\"><a href=\"#Taligent\" class=\"headerlink\" title=\"Taligent\"></a>Taligent</h3><p>在学术讨论中,Cocoa 的 Model-View-Controller 通常被成为 Model-View-Presenter。这两个是相同的,除了 Cocoa 里叫 Controllers 而不是 Presenters。”Presenter” 通过设置场景,调度动作体现它们的角色。某些情况下,Presenter 可能称为 Supervising Controller,你可以把 Model-View-Supervising Controller 简单地理解成 Model-View-Controller。<br>Model-View-Presenter 起源于 <a href=\"https://en.wikipedia.org/wiki/Taligent\">Taligent</a> 项目。<a href=\"http://www.wildcrest.com/Potel/Portfolio/mvp.pdf\">MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java</a>,这个引用最多的论文,成文于 1997 年。但<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/classes/TGUIPresenter.html\">文档显示</a>,至少早在 1995 年,Taligent 中的某些类就实现这个模式了。<br>Taligent 起初是苹果内部以”pink”为代号的项目,目的是为了开发一个操作系统以代替 <a href=\"https://en.wikipedia.org/wiki/System_7\">System 7</a>。 这个项目有一系列有名的开发、管理问题,和苹果同期撤掉的 <a href=\"https://en.wikipedia.org/wiki/Copland_(operating_system\">Copland</a>) 是难兄难弟。Taligent 后来得以在 IBM 延续,推出了 CommonPoint 应用框架,而不是一个操作系统,并于 1998 年被关掉。 </p>\n<p><em><a href=\"https://www.wired.com/1993/02/taligent/\">This Wired article from 1993</a> 给出了一个有意思的见解,Taligent 的明显膨胀和内讧导致了它的失败。</em></p>\n<p>尽管 NeXTStep 早于 Taligent,AppKit 中的众多控制器类(定义在 AppKit 的 Model-View-Presenter 设计模式下的),直到 1996 年 NeXTStep 4 才出现( NeXTStep 的一个重要的重新设计版本,第一个保留<code>NS</code>前缀直今)。我不知道 NeXTStep 是否借鉴了 Taligent,可能这是持续发展的结果,也可能几家公司雇佣了相同团队里的成员。站在 2017 年,我能说的就是 Taligent 是第一个发行的。 </p>\n<p><em>1995 年的 <a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/index.html\">Taligent documentation</a> 读起来令人着迷。<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/WM/WM_3.html\">Guide to Designing Programs</a> 讨论了许多有关应用程序设计的想法。但是,<a href=\"https://root.cern.ch/TaligentDocs/TaligentOnline/DocumentRoot/1.0/Docs/books/PF/PF_1.html\">Programming with the Presentation Framework 指南</a> 却很烂:它令人困惑、过于技术化、难以实现。</em></p>\n<h3 id=\"The-Controller-Problem\"><a href=\"#The-Controller-Problem\" class=\"headerlink\" title=\"The Controller Problem\"></a>The Controller Problem</h3><p>理解 Cocoa 的 Model-View-Controller 作为 Model-View-Presenter 的变种(Presenter 或者 Supervising Controller,要负责其下所有 view 的生命周期,动作和变化的通知)很重要,因为它导致了这种模式里的最大问题:”The Controller Problem”。<br>The Controller Problem,也叫做”Massive/Huge/Giant View Controllers”,是指:Cocoa 里的 controllers 有着强烈变大的倾向,要负责和 view 相关的事情,虽然跟功能和数据的独立性无关。大多数大项目有许多超过 2000 行的 controller 类。<br>说明白点,问题不在于 controller 代码的多少,而是它们增大的方式:Cocoa 的控制器聚合了许多跟它们有关或无关的事情。一个控制器可能负责几个或多个 view,每个 view 有自己的结构,配置,数据展示,数据更新,布局,动画跟动作,和其他要交给父控制器维护的状态。<br>独立和依赖大规模得混合在一起是维护的噩梦。大量代码使得实际的依赖和无关的功能难以区分。 Controller 通常难以测试(由于 app 和 包状态管理难以分离),规模和半独立的结合使得问题更糟。<br>Controller Problem 的解决办法是持续地将大的 controller 重构成小的、简单的 controller。这可能需要重新设计、重新思考数据结构,以解开、排除 controller 间的依赖,并设计出在几个 controller 间通信的可行实现。这能做到,不过有许多工作量,也有风险:带来新的 bug,依然难以测试,除此之外,交给用户的也不会有新的功能。</p>\n<h3 id=\"Bindings\"><a href=\"#Bindings\" class=\"headerlink\" title=\"Bindings\"></a>Bindings</h3><p>Apple 早就知道 Controller Problem 了,因为 Mac OS X 10.3 推出了 Cocoa Bindings。<br>绑定是两个组件间建立路径,通常是数据源和数据的观察者。绑定允许组件间不通过额外的代码实现交流。Cocoa bindings 里两个组件间建立的路径称为”key-path”。指明了 controller 至 model 的属性(管理 view 状态)的 key-path,绑定就能显著改善或消除 Controller Problem。</p>\n<p><img src=\"https://www.cocoawithlove.com/assets/blog/cocoa_bindings_mvc.svg\" alt=\"\"></p>\n<p><em>Code paths through the Controller are replaced by Bindings</em></p>\n<p>推出十多年后,Cocoa Bindings 都被人忘了。AppKit 你仍然可以用到,它们也没被废弃。不过它们从没被引入到 UIKit,反映出在使界面编程更简单这点上,它们不够成功。<br>我认为 Bindings 能解决问题,某些情况下解决得挺好,特别是<code>NSArrayController</code>驱动一个<code>NSTableView</code>。我也能理解它们为什么没能征服 Mac 编程。<br>Cocoa Bindings 的优势(试图控制器更少的代码)被 Interface Builder 的检查面板里的众多设置项做到了。可这令人有些困惑:代码里找不到,难以搜索(虽然 Xcode 也能搜索 XIB 文件了),很难 debug(数据变化没有堆栈跟踪),不容易教给新人(不愿意在检查面板里找),比起代码来更隐秘(XIB 里不能加注释),也会导致 Interface Builder 的本地化和版本控制合并的问题。<br>我觉得 Cocoa Bindings 的失败依然保留了添加自定义转换和自定义属性的困难。这些本来都能解决,但是注册转换和暴露 bindings 字典却令人厌倦。不用 bindings 在 controller 传数据更简单 ,也就是说 Bindings 想解决最简单的问题(本来也不用解决),却没有触及到更难的问题。</p>\n<h3 id=\"Something-new\"><a href=\"#Something-new\" class=\"headerlink\" title=\"Something new?\"></a>Something new?</h3><p>自 Cocoa Bindings 开始于 Mac OS X 10.3 以后,Apple 也没有再明确的尝试改变 Cocoa 的设计模式。<br>Storyboards 开始于 iOS 5 和 Mac OS X 10.10,不过 storyboards 也不是对设计模式的更换和改进。Storyboards 鼓励使用<code>NS</code>/<code>UIViewController</code>,加强了 Model-View-Presenter 模式。Storyboards 确实鼓励更小,更专注的 view controller,也减少了一点 “Presentation” 带来的页面设置和过渡的负担。但是,既然能通过 Interface Builder 配置,也就表现出了一系列 Cocoa Bindings 想解决的问题。<br>对想要设计模式更给力的某些人来说,Storyboards 没有提供新东西。<br>现在确实有些应用设计的新想法。Apple 之外,有 <a href=\"https://www.cocoawithlove.com/blog/reactive-programming-what-and-why.html\">Reactive Programming</a>(能实现 Bindings 的大部分功能),<a href=\"https://www.objc.io/issues/13-architecture/mvvm/\">Model-View-ViewModel</a>(把 controller 减少的功能转移到了跟 view 接近的 model 上),<a href=\"http://reswift.github.io/ReSwift/master/\">unidirectional dataflow</a>(减少 binding 需求,在整个 app 间广播所有的数据变化),这些在不同圈子都广受欢迎。<br>有些框架做得完全不一样,像 <a href=\"https://facebook.github.io/react-native/\">React Native</a> 或 <a href=\"https://github.com/salutis/swift-elm\">Swift-Elm</a>,舍弃了 Swift 或 Cocoa,也带来了无法忽视的不足。<br>这些能否影响到官方的 Cocoa app 开发,暂时还不清晰。Swift 表明 Apple 偶尔想改些东西,也有争论说 Swift 增加了设计模式的需求或利用 Swift 语言优势的 view 框架。不过,苹果开发一个仅 Swift 的框架,还需要时间。</p>\n<h3 id=\"Conclusion\"><a href=\"#Conclusion\" class=\"headerlink\" title=\"Conclusion\"></a>Conclusion</h3><p>如果我们以 NeXTStep 4 作为 Cocoa 现在 Model-View-Controller 模式的开端,那到现在就 20 年了。它还可用,虽然有它的缺点,也不像曾经那么令人激动或高效了。<br>Apple 很早就尝试过 Cocoa Bindings,这唯一的对设计模式的改进了。人们对它的接纳程度不一,它也不会带至 Apple 的新平台了。<br>我没有任何 AppKit 或 UIKit 团队的内部尝试的消息,未来 Apple 也不是随时都想做一些激动人心的改动。许多第三方框架想改进 Cocoa 整个设计模式,但还没有哪个能达成一致。我觉得这些努力反映出了人们对某些改进上的关注。</p>\n<hr>\n<p><span id=\"Fowler\">马丁·福勒,软件工程师,也是一个软件开发方面的著作者和国际知名演说家,专注于面向对象分析与设计,统一建模语言,领域建模,以及敏捷软件开发方法,包括极限编程。《重构—改善既有代码的设计》的作者,ThoughtWorks 的首席科学家。来自<a href=\"https://zh.wikipedia.org/wiki/%E9%A9%AC%E4%B8%81%C2%B7%E7%A6%8F%E5%8B%92\">维基百科</a>。</span></p>\n"},{"title":"IOS 的全屏旋转","date":"2017-04-06T16:00:00.000Z","author":"李卓","_content":"\n![心无挂碍](http://upload-images.jianshu.io/upload_images/1612119-4d748c0bd32e0439.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 假如有这样的需求\n\n如果我们的项目中有这样的需求,整个 app 的屏幕方向仅支持一种,比如 Portrait(如下图)。但是在某一个页面却需要可以支持多种方向,比如一个支持全屏观看的视频播放页面。有两张方式可以实现这样的需求,以下两种方式只是提供一个简单的实现。\n\n![Portrait](http://upload-images.jianshu.io/upload_images/1612119-41e9797062d554e3.png)\n\n---\n\n### 第一种方式\n\n第一种方式就是使用强制旋转屏幕、更改屏幕支持方向的方式来实现上述需求。\n\n* AppDelegate.h 中增加一个属性\n\n```\n@interface AppDelegate : UIResponder <UIApplicationDelegate>\n\n@property (strong, nonatomic) UIWindow *window;\n//\n@property (nonatomic, assign) BOOL allowLandscapeRight;\n\n@end\n\n```\n\n* AppDelegate.m 中添加 application:supportedInterfaceOrientationsForWindow: 方法监听。这个示例中仅仅支持屏幕向左旋转(即home键在右)的方式。\n\n```\n- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window\n{\n if (self.allowLandscapeRight) {\n return UIInterfaceOrientationMaskLandscapeRight;\n }\n\n return UIInterfaceOrientationMaskPortrait;\n}\n\n```\n\n* 视频播放的ViewController中\n\n```\n- (void)updateOriention\n{\n AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;\n\n if (self.isFullScreeen) {\n appDelegate.allowLandscapeRight = YES;\n\n // 强制旋转成全屏\n NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationLandscapeLeft];\n [[UIDevice currentDevice]setValue:value forKey:@\"orientation\"];\n } else {\n appDelegate.allowLandscapeRight = NO;\n\n // 强制旋转成竖屏\n NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationPortrait];\n [[UIDevice currentDevice]setValue:value forKey:@\"orientation\"];\n }\n}\n```\n\n### 第二种方式\n\n第二种方式则是在展示视频View中通过监听屏幕的旋转、对播放界面进行重新布局来实现。\n\n* 监测设备方向\n\n```\n- (void)addNotifications {\n // 监测设备方向\n [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];\n [[NSNotificationCenter defaultCenter] addObserver:self\n selector:@selector(onDeviceOrientationChange)\n name:UIDeviceOrientationDidChangeNotification\n object:nil];\n\nname:UIApplicationDidChangeStatusBarOrientationNotification\n object:nil];\n}\n\n```\n\n* 屏幕发生旋转后调用这里\n\n```\n- (void)onDeviceOrientationChange {\n UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;\n UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;\n\n if (orientation == UIDeviceOrientationUnknown ||\n orientation == UIDeviceOrientationPortraitUpsideDown ||\n orientation == UIDeviceOrientationLandscapeRight ||\n orientation == UIDeviceOrientationFaceUp ||\n orientation == UIDeviceOrientationFaceDown){\n return;\n }\n\n switch (interfaceOrientation) {\n case UIInterfaceOrientationPortrait:{\n if (self.isFullScreen) {\n [self toOrientation:UIInterfaceOrientationPortrait];\n\n }\n }\n break;\n case UIInterfaceOrientationLandscapeLeft:{\n if (self.isFullScreen == NO) {\n [self toOrientation:UIInterfaceOrientationLandscapeLeft];\n self.isFullScreen = YES;\n } else {\n [self toOrientation:UIInterfaceOrientationLandscapeLeft];\n }\n\n }\n break;\n default:\n break;\n }\n}\n```\n\n* 旋转代码\n\n```\n- (void)toOrientation:(UIInterfaceOrientation)orientation {\n\n UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;\n // 判断如果当前方向和要旋转的方向一致,那么不做任何操作\n if (currentOrientation == orientation) { return; }\n\n if (orientation != UIInterfaceOrientationPortrait) {\n /*\n 这里对子控件重新布局\n */\n }\n\n [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];\n // 获取旋转状态条需要的时间:\n [UIView beginAnimations:nil context:nil];\n [UIView setAnimationDuration:0.3];\n self.transform = CGAffineTransformIdentity;\n self.transform = [self getTransformRotationAngle];\n\n [UIView commitAnimations];\n}\n```\n\n---\n\n### 总结\n当然,上述两种方式仅仅提供了一些解决方案和思路。实际使用的时候,要根据需求来做一些跟全面的逻辑判断。\n","source":"_posts/IOS 的全屏旋转.md","raw":"---\ntitle: IOS 的全屏旋转\ndate: 2017-04-07\ncategory: ios\nauthor: 李卓\n---\n\n![心无挂碍](http://upload-images.jianshu.io/upload_images/1612119-4d748c0bd32e0439.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n### 假如有这样的需求\n\n如果我们的项目中有这样的需求,整个 app 的屏幕方向仅支持一种,比如 Portrait(如下图)。但是在某一个页面却需要可以支持多种方向,比如一个支持全屏观看的视频播放页面。有两张方式可以实现这样的需求,以下两种方式只是提供一个简单的实现。\n\n![Portrait](http://upload-images.jianshu.io/upload_images/1612119-41e9797062d554e3.png)\n\n---\n\n### 第一种方式\n\n第一种方式就是使用强制旋转屏幕、更改屏幕支持方向的方式来实现上述需求。\n\n* AppDelegate.h 中增加一个属性\n\n```\n@interface AppDelegate : UIResponder <UIApplicationDelegate>\n\n@property (strong, nonatomic) UIWindow *window;\n//\n@property (nonatomic, assign) BOOL allowLandscapeRight;\n\n@end\n\n```\n\n* AppDelegate.m 中添加 application:supportedInterfaceOrientationsForWindow: 方法监听。这个示例中仅仅支持屏幕向左旋转(即home键在右)的方式。\n\n```\n- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window\n{\n if (self.allowLandscapeRight) {\n return UIInterfaceOrientationMaskLandscapeRight;\n }\n\n return UIInterfaceOrientationMaskPortrait;\n}\n\n```\n\n* 视频播放的ViewController中\n\n```\n- (void)updateOriention\n{\n AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;\n\n if (self.isFullScreeen) {\n appDelegate.allowLandscapeRight = YES;\n\n // 强制旋转成全屏\n NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationLandscapeLeft];\n [[UIDevice currentDevice]setValue:value forKey:@\"orientation\"];\n } else {\n appDelegate.allowLandscapeRight = NO;\n\n // 强制旋转成竖屏\n NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationPortrait];\n [[UIDevice currentDevice]setValue:value forKey:@\"orientation\"];\n }\n}\n```\n\n### 第二种方式\n\n第二种方式则是在展示视频View中通过监听屏幕的旋转、对播放界面进行重新布局来实现。\n\n* 监测设备方向\n\n```\n- (void)addNotifications {\n // 监测设备方向\n [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];\n [[NSNotificationCenter defaultCenter] addObserver:self\n selector:@selector(onDeviceOrientationChange)\n name:UIDeviceOrientationDidChangeNotification\n object:nil];\n\nname:UIApplicationDidChangeStatusBarOrientationNotification\n object:nil];\n}\n\n```\n\n* 屏幕发生旋转后调用这里\n\n```\n- (void)onDeviceOrientationChange {\n UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;\n UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;\n\n if (orientation == UIDeviceOrientationUnknown ||\n orientation == UIDeviceOrientationPortraitUpsideDown ||\n orientation == UIDeviceOrientationLandscapeRight ||\n orientation == UIDeviceOrientationFaceUp ||\n orientation == UIDeviceOrientationFaceDown){\n return;\n }\n\n switch (interfaceOrientation) {\n case UIInterfaceOrientationPortrait:{\n if (self.isFullScreen) {\n [self toOrientation:UIInterfaceOrientationPortrait];\n\n }\n }\n break;\n case UIInterfaceOrientationLandscapeLeft:{\n if (self.isFullScreen == NO) {\n [self toOrientation:UIInterfaceOrientationLandscapeLeft];\n self.isFullScreen = YES;\n } else {\n [self toOrientation:UIInterfaceOrientationLandscapeLeft];\n }\n\n }\n break;\n default:\n break;\n }\n}\n```\n\n* 旋转代码\n\n```\n- (void)toOrientation:(UIInterfaceOrientation)orientation {\n\n UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;\n // 判断如果当前方向和要旋转的方向一致,那么不做任何操作\n if (currentOrientation == orientation) { return; }\n\n if (orientation != UIInterfaceOrientationPortrait) {\n /*\n 这里对子控件重新布局\n */\n }\n\n [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];\n // 获取旋转状态条需要的时间:\n [UIView beginAnimations:nil context:nil];\n [UIView setAnimationDuration:0.3];\n self.transform = CGAffineTransformIdentity;\n self.transform = [self getTransformRotationAngle];\n\n [UIView commitAnimations];\n}\n```\n\n---\n\n### 总结\n当然,上述两种方式仅仅提供了一些解决方案和思路。实际使用的时候,要根据需求来做一些跟全面的逻辑判断。\n","slug":"IOS 的全屏旋转","published":1,"updated":"2017-04-07T03:43:00.000Z","_id":"cj17abp1u0000dx5fi11kbpo9","comments":1,"layout":"post","photos":[],"link":"","content":"<p><img src=\"http://upload-images.jianshu.io/upload_images/1612119-4d748c0bd32e0439.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"心无挂碍\"></p>\n<h3 id=\"假如有这样的需求\"><a href=\"#假如有这样的需求\" class=\"headerlink\" title=\"假如有这样的需求\"></a>假如有这样的需求</h3><p>如果我们的项目中有这样的需求,整个 app 的屏幕方向仅支持一种,比如 Portrait(如下图)。但是在某一个页面却需要可以支持多种方向,比如一个支持全屏观看的视频播放页面。有两张方式可以实现这样的需求,以下两种方式只是提供一个简单的实现。</p>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/1612119-41e9797062d554e3.png\" alt=\"Portrait\"></p>\n<hr>\n<h3 id=\"第一种方式\"><a href=\"#第一种方式\" class=\"headerlink\" title=\"第一种方式\"></a>第一种方式</h3><p>第一种方式就是使用强制旋转屏幕、更改屏幕支持方向的方式来实现上述需求。</p>\n<ul>\n<li>AppDelegate.h 中增加一个属性</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div></pre></td><td class=\"code\"><pre><div class=\"line\">@interface AppDelegate : UIResponder <UIApplicationDelegate></div><div class=\"line\"></div><div class=\"line\">@property (strong, nonatomic) UIWindow *window;</div><div class=\"line\">//</div><div class=\"line\">@property (nonatomic, assign) BOOL allowLandscapeRight;</div><div class=\"line\"></div><div class=\"line\">@end</div></pre></td></tr></table></figure>\n<ul>\n<li>AppDelegate.m 中添加 application:supportedInterfaceOrientationsForWindow: 方法监听。这个示例中仅仅支持屏幕向左旋转(即home键在右)的方式。</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window</div><div class=\"line\">{</div><div class=\"line\"> if (self.allowLandscapeRight) {</div><div class=\"line\"> return UIInterfaceOrientationMaskLandscapeRight;</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> return UIInterfaceOrientationMaskPortrait;</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>视频播放的ViewController中</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)updateOriention</div><div class=\"line\">{</div><div class=\"line\"> AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;</div><div class=\"line\"></div><div class=\"line\"> if (self.isFullScreeen) {</div><div class=\"line\"> appDelegate.allowLandscapeRight = YES;</div><div class=\"line\"></div><div class=\"line\"> // 强制旋转成全屏</div><div class=\"line\"> NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationLandscapeLeft];</div><div class=\"line\"> [[UIDevice currentDevice]setValue:value forKey:@"orientation"];</div><div class=\"line\"> } else {</div><div class=\"line\"> appDelegate.allowLandscapeRight = NO;</div><div class=\"line\"></div><div class=\"line\"> // 强制旋转成竖屏</div><div class=\"line\"> NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationPortrait];</div><div class=\"line\"> [[UIDevice currentDevice]setValue:value forKey:@"orientation"];</div><div class=\"line\"> }</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<h3 id=\"第二种方式\"><a href=\"#第二种方式\" class=\"headerlink\" title=\"第二种方式\"></a>第二种方式</h3><p>第二种方式则是在展示视频View中通过监听屏幕的旋转、对播放界面进行重新布局来实现。</p>\n<ul>\n<li>监测设备方向</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)addNotifications {</div><div class=\"line\"> // 监测设备方向</div><div class=\"line\"> [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];</div><div class=\"line\"> [[NSNotificationCenter defaultCenter] addObserver:self</div><div class=\"line\"> selector:@selector(onDeviceOrientationChange)</div><div class=\"line\"> name:UIDeviceOrientationDidChangeNotification</div><div class=\"line\"> object:nil];</div><div class=\"line\"></div><div class=\"line\">name:UIApplicationDidChangeStatusBarOrientationNotification</div><div class=\"line\"> object:nil];</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>屏幕发生旋转后调用这里</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div><div class=\"line\">27</div><div class=\"line\">28</div><div class=\"line\">29</div><div class=\"line\">30</div><div class=\"line\">31</div><div class=\"line\">32</div><div class=\"line\">33</div><div class=\"line\">34</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)onDeviceOrientationChange {</div><div class=\"line\"> UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;</div><div class=\"line\"> UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;</div><div class=\"line\"></div><div class=\"line\"> if (orientation == UIDeviceOrientationUnknown ||</div><div class=\"line\"> orientation == UIDeviceOrientationPortraitUpsideDown ||</div><div class=\"line\"> orientation == UIDeviceOrientationLandscapeRight ||</div><div class=\"line\"> orientation == UIDeviceOrientationFaceUp ||</div><div class=\"line\"> orientation == UIDeviceOrientationFaceDown){</div><div class=\"line\"> return;</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> switch (interfaceOrientation) {</div><div class=\"line\"> case UIInterfaceOrientationPortrait:{</div><div class=\"line\"> if (self.isFullScreen) {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationPortrait];</div><div class=\"line\"></div><div class=\"line\"> }</div><div class=\"line\"> }</div><div class=\"line\"> break;</div><div class=\"line\"> case UIInterfaceOrientationLandscapeLeft:{</div><div class=\"line\"> if (self.isFullScreen == NO) {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationLandscapeLeft];</div><div class=\"line\"> self.isFullScreen = YES;</div><div class=\"line\"> } else {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationLandscapeLeft];</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> }</div><div class=\"line\"> break;</div><div class=\"line\"> default:</div><div class=\"line\"> break;</div><div class=\"line\"> }</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>旋转代码</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)toOrientation:(UIInterfaceOrientation)orientation {</div><div class=\"line\"></div><div class=\"line\"> UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;</div><div class=\"line\"> // 判断如果当前方向和要旋转的方向一致,那么不做任何操作</div><div class=\"line\"> if (currentOrientation == orientation) { return; }</div><div class=\"line\"></div><div class=\"line\"> if (orientation != UIInterfaceOrientationPortrait) {</div><div class=\"line\"> /*</div><div class=\"line\"> 这里对子控件重新布局</div><div class=\"line\"> */</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];</div><div class=\"line\"> // 获取旋转状态条需要的时间:</div><div class=\"line\"> [UIView beginAnimations:nil context:nil];</div><div class=\"line\"> [UIView setAnimationDuration:0.3];</div><div class=\"line\"> self.transform = CGAffineTransformIdentity;</div><div class=\"line\"> self.transform = [self getTransformRotationAngle];</div><div class=\"line\"></div><div class=\"line\"> [UIView commitAnimations];</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<hr>\n<h3 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h3><p>当然,上述两种方式仅仅提供了一些解决方案和思路。实际使用的时候,要根据需求来做一些跟全面的逻辑判断。</p>\n","excerpt":"","more":"<p><img src=\"http://upload-images.jianshu.io/upload_images/1612119-4d748c0bd32e0439.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240\" alt=\"心无挂碍\"></p>\n<h3 id=\"假如有这样的需求\"><a href=\"#假如有这样的需求\" class=\"headerlink\" title=\"假如有这样的需求\"></a>假如有这样的需求</h3><p>如果我们的项目中有这样的需求,整个 app 的屏幕方向仅支持一种,比如 Portrait(如下图)。但是在某一个页面却需要可以支持多种方向,比如一个支持全屏观看的视频播放页面。有两张方式可以实现这样的需求,以下两种方式只是提供一个简单的实现。</p>\n<p><img src=\"http://upload-images.jianshu.io/upload_images/1612119-41e9797062d554e3.png\" alt=\"Portrait\"></p>\n<hr>\n<h3 id=\"第一种方式\"><a href=\"#第一种方式\" class=\"headerlink\" title=\"第一种方式\"></a>第一种方式</h3><p>第一种方式就是使用强制旋转屏幕、更改屏幕支持方向的方式来实现上述需求。</p>\n<ul>\n<li>AppDelegate.h 中增加一个属性</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div></pre></td><td class=\"code\"><pre><div class=\"line\">@interface AppDelegate : UIResponder <UIApplicationDelegate></div><div class=\"line\"></div><div class=\"line\">@property (strong, nonatomic) UIWindow *window;</div><div class=\"line\">//</div><div class=\"line\">@property (nonatomic, assign) BOOL allowLandscapeRight;</div><div class=\"line\"></div><div class=\"line\">@end</div></pre></td></tr></table></figure>\n<ul>\n<li>AppDelegate.m 中添加 application:supportedInterfaceOrientationsForWindow: 方法监听。这个示例中仅仅支持屏幕向左旋转(即home键在右)的方式。</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window</div><div class=\"line\">{</div><div class=\"line\"> if (self.allowLandscapeRight) {</div><div class=\"line\"> return UIInterfaceOrientationMaskLandscapeRight;</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> return UIInterfaceOrientationMaskPortrait;</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>视频播放的ViewController中</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)updateOriention</div><div class=\"line\">{</div><div class=\"line\"> AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;</div><div class=\"line\"></div><div class=\"line\"> if (self.isFullScreeen) {</div><div class=\"line\"> appDelegate.allowLandscapeRight = YES;</div><div class=\"line\"></div><div class=\"line\"> // 强制旋转成全屏</div><div class=\"line\"> NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationLandscapeLeft];</div><div class=\"line\"> [[UIDevice currentDevice]setValue:value forKey:@"orientation"];</div><div class=\"line\"> } else {</div><div class=\"line\"> appDelegate.allowLandscapeRight = NO;</div><div class=\"line\"></div><div class=\"line\"> // 强制旋转成竖屏</div><div class=\"line\"> NSNumber *value = [NSNumber numberWithInt:UIDeviceOrientationPortrait];</div><div class=\"line\"> [[UIDevice currentDevice]setValue:value forKey:@"orientation"];</div><div class=\"line\"> }</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<h3 id=\"第二种方式\"><a href=\"#第二种方式\" class=\"headerlink\" title=\"第二种方式\"></a>第二种方式</h3><p>第二种方式则是在展示视频View中通过监听屏幕的旋转、对播放界面进行重新布局来实现。</p>\n<ul>\n<li>监测设备方向</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)addNotifications {</div><div class=\"line\"> // 监测设备方向</div><div class=\"line\"> [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];</div><div class=\"line\"> [[NSNotificationCenter defaultCenter] addObserver:self</div><div class=\"line\"> selector:@selector(onDeviceOrientationChange)</div><div class=\"line\"> name:UIDeviceOrientationDidChangeNotification</div><div class=\"line\"> object:nil];</div><div class=\"line\"></div><div class=\"line\">name:UIApplicationDidChangeStatusBarOrientationNotification</div><div class=\"line\"> object:nil];</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>屏幕发生旋转后调用这里</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div><div class=\"line\">22</div><div class=\"line\">23</div><div class=\"line\">24</div><div class=\"line\">25</div><div class=\"line\">26</div><div class=\"line\">27</div><div class=\"line\">28</div><div class=\"line\">29</div><div class=\"line\">30</div><div class=\"line\">31</div><div class=\"line\">32</div><div class=\"line\">33</div><div class=\"line\">34</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)onDeviceOrientationChange {</div><div class=\"line\"> UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;</div><div class=\"line\"> UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;</div><div class=\"line\"></div><div class=\"line\"> if (orientation == UIDeviceOrientationUnknown ||</div><div class=\"line\"> orientation == UIDeviceOrientationPortraitUpsideDown ||</div><div class=\"line\"> orientation == UIDeviceOrientationLandscapeRight ||</div><div class=\"line\"> orientation == UIDeviceOrientationFaceUp ||</div><div class=\"line\"> orientation == UIDeviceOrientationFaceDown){</div><div class=\"line\"> return;</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> switch (interfaceOrientation) {</div><div class=\"line\"> case UIInterfaceOrientationPortrait:{</div><div class=\"line\"> if (self.isFullScreen) {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationPortrait];</div><div class=\"line\"></div><div class=\"line\"> }</div><div class=\"line\"> }</div><div class=\"line\"> break;</div><div class=\"line\"> case UIInterfaceOrientationLandscapeLeft:{</div><div class=\"line\"> if (self.isFullScreen == NO) {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationLandscapeLeft];</div><div class=\"line\"> self.isFullScreen = YES;</div><div class=\"line\"> } else {</div><div class=\"line\"> [self toOrientation:UIInterfaceOrientationLandscapeLeft];</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> }</div><div class=\"line\"> break;</div><div class=\"line\"> default:</div><div class=\"line\"> break;</div><div class=\"line\"> }</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<ul>\n<li>旋转代码</li>\n</ul>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><div class=\"line\">1</div><div class=\"line\">2</div><div class=\"line\">3</div><div class=\"line\">4</div><div class=\"line\">5</div><div class=\"line\">6</div><div class=\"line\">7</div><div class=\"line\">8</div><div class=\"line\">9</div><div class=\"line\">10</div><div class=\"line\">11</div><div class=\"line\">12</div><div class=\"line\">13</div><div class=\"line\">14</div><div class=\"line\">15</div><div class=\"line\">16</div><div class=\"line\">17</div><div class=\"line\">18</div><div class=\"line\">19</div><div class=\"line\">20</div><div class=\"line\">21</div></pre></td><td class=\"code\"><pre><div class=\"line\">- (void)toOrientation:(UIInterfaceOrientation)orientation {</div><div class=\"line\"></div><div class=\"line\"> UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;</div><div class=\"line\"> // 判断如果当前方向和要旋转的方向一致,那么不做任何操作</div><div class=\"line\"> if (currentOrientation == orientation) { return; }</div><div class=\"line\"></div><div class=\"line\"> if (orientation != UIInterfaceOrientationPortrait) {</div><div class=\"line\"> /*</div><div class=\"line\"> 这里对子控件重新布局</div><div class=\"line\"> */</div><div class=\"line\"> }</div><div class=\"line\"></div><div class=\"line\"> [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO];</div><div class=\"line\"> // 获取旋转状态条需要的时间:</div><div class=\"line\"> [UIView beginAnimations:nil context:nil];</div><div class=\"line\"> [UIView setAnimationDuration:0.3];</div><div class=\"line\"> self.transform = CGAffineTransformIdentity;</div><div class=\"line\"> self.transform = [self getTransformRotationAngle];</div><div class=\"line\"></div><div class=\"line\"> [UIView commitAnimations];</div><div class=\"line\">}</div></pre></td></tr></table></figure>\n<hr>\n<h3 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h3><p>当然,上述两种方式仅仅提供了一些解决方案和思路。实际使用的时候,要根据需求来做一些跟全面的逻辑判断。</p>\n"}],"PostAsset":[],"PostCategory":[{"post_id":"cj17ab72t0005as5fkflvt8di","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab732000bas5fen7wnl64"},{"post_id":"cj17ab72i0000as5frzaijj7k","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab736000fas5fgyux9r8e"},{"post_id":"cj17ab72w0006as5fq6rdtrmo","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab738000ias5fhvfngp8o"},{"post_id":"cj17ab72z000aas5fwwwa8bbu","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab73a000mas5fqwpgshhy"},{"post_id":"cj17ab72n0001as5f28znqyx4","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab73a000oas5f3tmx0mn4"},{"post_id":"cj17ab72s0004as5fo1la6bgu","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab73a000qas5f54sewqd3"},{"post_id":"cj17ab738000jas5fhjkvi84h","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17ab73c000tas5f8asyzisp"},{"post_id":"cj17ab732000cas5flam4bq9s","category_id":"cj17ab739000kas5fxjdl2i3f","_id":"cj17ab73c000uas5fpnfq34ac"},{"post_id":"cj17ab736000gas5fgpt2ej52","category_id":"cj17ab739000kas5fxjdl2i3f","_id":"cj17ab73c000xas5fh270qyha"},{"post_id":"cj17abp1u0000dx5fi11kbpo9","category_id":"cj17ab72p0002as5fdw0b8kpz","_id":"cj17abp210001dx5ffoglq30t"}],"PostTag":[{"post_id":"cj17ab72i0000as5frzaijj7k","tag_id":"cj17ab72r0003as5fxw6k9r3o","_id":"cj17ab72z0009as5fvp9ae2sz"},{"post_id":"cj17ab72s0004as5fo1la6bgu","tag_id":"cj17ab72y0008as5f86t8mpcs","_id":"cj17ab737000has5f1n0v2200"},{"post_id":"cj17ab72t0005as5fkflvt8di","tag_id":"cj17ab735000eas5fen67rzpr","_id":"cj17ab73a000nas5fw71mrisi"},{"post_id":"cj17ab732000cas5flam4bq9s","tag_id":"cj17ab739000las5feqr2uu3x","_id":"cj17ab73c000sas5fkvzyxyu6"},{"post_id":"cj17ab736000gas5fgpt2ej52","tag_id":"cj17ab73a000ras5fdh5ae7z5","_id":"cj17ab73c000was5f3og1pso4"},{"post_id":"cj17ab738000jas5fhjkvi84h","tag_id":"cj17ab73c000vas5faymcynzx","_id":"cj17ab73c000yas5fg2l7jrh4"}],"Tag":[{"name":"GCD","_id":"cj17ab72r0003as5fxw6k9r3o"},{"name":"UIImageView","_id":"cj17ab72y0008as5f86t8mpcs"},{"name":"feature-flow","_id":"cj17ab735000eas5fen67rzpr"},{"name":"live-audio","_id":"cj17ab739000las5feqr2uu3x"},{"name":"preventViewScroll","_id":"cj17ab73a000ras5fdh5ae7z5"},{"name":"Cocoa","_id":"cj17ab73c000vas5faymcynzx"}]}}