我在 2005 年开始和我一个朋友开发一个网页游戏:Simerion. 这是一个混合了 RPG 和经营类的游戏。想法是让玩家成为新开发星球的殖民地移民。 每天每个玩家都要选择一个职业,然后主要很简单:RolePlay. 每个人都可以做他想做的,做什么都可以. 捡主要的说,就是一个理想的游戏,但是实现起来却不那么回事了.
我在 5 年的设计 / 开发后离开了 Simerion. 那是 2009 年。 我会在后面说明为什么. 但是我得借这次机会做个项目的总结。在我的观点下,就是想做个反馈下我们在这个项目里所学到的特别还有我们所做的错误决定. 特别还希望帮助正在做类似项目的人. 首先, 我得解释下这个游戏还没停止开发,不过是我个人退出了而已. 所以如果你们谁想加入开发团队,请别犹豫上这里:www.Simerion.fr
现在首先先来一个概览:
-2005: Wett 和我从抽屉底里找到我 2 年前画的草图. 然后接下来的 5 个月我们就把我们所有的点子都写在了论坛和 WIKI 上. 但是后来因为学业的关系我们暂停了项目 (Prepa: 就是法国上工程师学院等高等学院前的预备班).
-2006 年 7 月:一年以后,我们又重新拿起项目,策划持续了一个月.
-2006 年 8 月: 我们终于开始开发了,并且我们加入了 Nainwak.
-2007 年 8 月:又是一年,在 3 月份后借着 Altheran 的帮助,我们成功的弄出了一个 Alpha 测试版本,那时玩家大概有 50 多个.
-2009 年 3 月:又是一年半,Beta 版出来了而我离开了团队。
在这项目期间,我发现了多人合作开发的重要性. 有各种原因,比如首先当有人失去积极性的时候可以互相鼓励. 并且有多个人的时候可以更轻松的跨过障碍. 像开发 SIMERION 这种网页游戏不应该闭门造车. 但是也要注意负面效应,如果有太多人参加的时候反而会产生负面的效果,所以应该要有数量正好的程序员,当然至于数量多少这是要考虑到他们的能力和时间做决定.
我们刚开始的时候是选择用 PHP + Ajax 来开发一个在网络上可以让玩家们互相互动的游戏. 为什么 PHP? 因为我比较熟悉 PHP 所以可以更简单的上手. 但是后来我发现范了个严重的错误,就是不应该选择这个语言。当初应该用 Java 或者 C++ 来制作一个真正地 MMORPG. 我们后来很快的就发现了 PHP 和 Ajax 的限制,特别是需要让玩家们实时的产生互动的时候,但是那时候要换语言已经太迟了. 所以在这里我严重建议大家要先参考全部可用的语言后再做慎重的决定!当我们开始开发一个大型的 WEB 项目时,每一个语言都有它的长处和短处,需要比较后做决定. 但是我们却没有这么做, 所以我们就作茧自缚了… 当在这种情况下,甚至应该先做一个 PoC(Proof of Concetp) 来确定一个语言,API,或者其他什么东西. 这样就可以避免那些不想要的 “惊喜” 并且可以知道哪些比起另外那些更好,更健壮.
另外一个我们做的错误决定是没有用那些已经做好的 Php 和 Ajax 框架. 但是我们太疯了,居然自己从 A-Z 做一个游戏引擎,还有自己的 Ajax 框架. 我们在重新造轮子的期间了浪费了超级多的时间. 另外一方面,我们甚至不知道这些工具已经被做好就放在那边给我们用,如果用上这些东西甚至能早点我们明白一些技术和技巧. 如果当时重新做的话,我绝对不会再重新造轮子了. 当开始一个大型的项目时,我觉得真的需要先看,比较那些可用的工具然后不要直接就低着头乱冲. 用那些已经存在的工具真的可以帮你赢的非常珍贵的时间.
使用 UML 图非常重要,当然在整个过程中想要严谨的把整个想法都写在纸上还是非常困难的, 但是到最后总是有益的,因为这样可以赢的非常多的时间. 虽然 UML 图只在我们不想修改想法的时候才有用。而且有些时候,UML 图却可能会经常产生疑问,所以可能会浪费非常多的时间
一个网页游戏的类还是非常复杂的. 第一眼看上去的时候, “全物品” 可能是一个不错的东西,但是类却不是数据库的好朋友。 说到类就是属性,说道属性就是背后数据库不停的更新和过于的负载. 用类和实例工作代表者需要长期的优化代码让服务器不会因为 SQL 请求而崩溃. 比如,如果我要载入一个 Simerion 里一个玩家的建筑,我们首先要载入一个定义它类型的物体 (class),然后载入另外一个定义它的实例 (建筑) 之后还有它的类. 然后有时候我们还需要给这个建筑定位,所以我们还需要加载这个建筑所在的地区 (另外一个表). 因为有很多星球所以还必须加载这个地区所在的星球 (又是一个表). 最后创建这个物体就变得非常吃资源,并且需要大堆的 SQL 请求. 想象下你还需要这个样子载入这整个建筑里的其他物体… 简直是服务器杀手阿。。。 所以我们就不得不不停地优化代码然后慢慢得就远离了平常的那 “全物品” 系统. 虽然原来那样非常有趣,但是当我们需要做一些需要面对非常多用户的页面时,性能就非常的重要. 我想绝对的解决办法是不存在的还有就是为了代码的可维护性必须放弃 “全 OOP”. 不过话说回来,关于和数据库的交互,必须找到一个平衡点,然后试着远离平时那种在创建物品的载入全部有关物品的做法.
我想这里应该需要看一些大型的框架看看他们是怎么做的,和数据库交互是件非常复杂且尖端的学问, 我想甚至在 5 年后,这些代码也没有最大化的优化过. 所以结论是,还是去看一些已经存在的工具看看他们是怎么做的,非常有帮助.
.
另外一件我们浪费了很多时间的事情就是长期的把旧的代码拿出来研究. 这说起来比作起来容易,特别是千万不要一开始就直接把代码优化到死!要慢慢的时候到了你就会觉得哪里该优化了. 我等下说下如何一步步工作的问题, 但是通过我开发 SIMERION 的经验来看,就是千万千万要先让代码运行起来就 OK 然后走到下一步而不是老是不停地把一些代码拿出来研究. 当这些代码可以运行了以后,就可以优化了,但是千万要先可运行然后才是低着头优化游戏的安全性和流畅性.
我们的开发周期肯定是不对的. 而这应该是我们 (我) 的失败. 5 年啊! 我们花了 5 年的时间在这个项目上而直到我离开居然都还没有一个可以公开给大家玩的游戏… 这也是我离开的其中一个原因. 这甚至可以说是对自己的剥削了. 我们选择了只有这个游戏完全可以玩了之后才公布游戏. 然而,Simerion 的主要想法是可以做任何事情,所以全部都是必须的还有全部都互相平衡. 所以只要游戏还没有完全完成就没办法公布一个可以玩的游戏. 这真是太大的错误啦! 当策划写好了以后,我认为,一个业余游戏应该要以最快的时间推出.
不止可以尽快得到玩家的回馈,而且还能激励团队坚持下去。 因为连续几个月甚至几年 (比如说我们) 没有其他人的意见和回馈真的是非常让人沮丧的, 所以激情自然也就消磨了。 还有就是不能想当然的把任务托付给团队的其他人,因为他们也同样在消磨着激情。 所以我强烈建议尽快推出可以进行游戏的版本(起码对业余项目来说), 要经常推出新的版本,而不是像我们那样在每个版本里都花费那么久的时间。
如果那样做的话就可能会保持一个玩家社区,玩家和开发者就能互相照应,开发者也能更能保持项目开发的热情。如果不这样的话,经过 N 个月 N 年后很可能会流产. 然后所有的努力都会白费。
就像我前面说的那样,在项目开始的时候就应该着手打造玩家社区。 他们可以帮助测试游戏,返回回馈,发表他们的主意… 等等, Simerion 由于没有发布可以玩的版本,所以我们在开发时就没有玩家社区来支持我们. 而这就有了个非常坏的结果. 所以我非常建议在开发初期就要有个社区,因为社区还可以吸引新的玩家,其中可能就会有人称为你项目组里的新成员。
虽然很不情愿,但是做一个完整又复杂的 RPG 游戏的确不是业余玩家随随便便就能做出来的。如果有人站出来反对我的话我会非常开心! 但是制作图片,装饰,背景,剧情,声音,任务 等等 全都需要非常多的投入,我 95% 的时间都同时作为程序员,编剧,美工存在,但是这个到了后来越来越无法支撑下去, 所以在队伍里应该要最少一个开发者,一个编剧和一个美工才能完成项目。 不能以为任何事情都要自己亲自完成。
在这点上,我是比较悲观的,我认为像 辐射,最终幻想,塞尔达传说,这些 “终极” 游戏是不可能被业余开发者完成的,我感觉上,一个业余游戏在开始的时候必须不能是雄心勃勃的,要慢慢来,一边开心的扩展项目一边要认识到是不可能和那些专业做游戏的公司比的。 一个业余的 “终极” 游戏是不可能实现的,因为有太多的工作要做了. 在 Simerionsa 上我们从策划开始就像的非常复杂,太理想化了。。。 成功的钥匙是在刚开始就找到创意和代码量之间的平衡点。
非常重要的一点,要持续保持游戏文档的更新,因为通常项目都会遇到需要暂停 N 个月的情况。 如果在那些时间之后再重新拿起以前的代码的话可能连自己都看不明白, 项目文档可以让人对项目有个整体的视角,让人可以更轻松的组织和认识到以前的想法, 在技术方面它可以解释游戏的运行方式,更可以解释为什么会选择某个算法到游戏里.
在重新回到项目的时候文档还可以帮你省下 N 多理解你和其他开发者写的代码上用的时间. 这些工具还可以帮助项目组新的成员更容易的加入进来。
在 Simerion 上因为这个项目代码实在是已经变成了一个如同毒气室一样的东西,几乎没有人会咬紧牙关一点点的理解那些缺少注释没有解释的代码。 所以缺少对项目的解释会阻碍新成员的加入。
我们在刚开始的时候就加入了 Nainwak 协会,他们帮我们部署游戏,支持我们并且给我们建议。 如果没有他们的帮助我们一定无法支持那么久的时间. 通过他们的帮助我们甚至还得到了参加游戏展示的机会。 我们还遇到了很多对我们游戏有兴趣的职业游戏开发者. 那真的是非常有意思的经历,所以我非常建议大家如果要做网页游戏的话就联系他们把。 这个协会的任务就是帮助业余的游戏开发者,而且他们做的非常好。
2009 年,我离开了项目。
那时候我完全放弃了 Simerion 的开发因为对我来说那时是在已经没有任何激情了。 开发这个的任务已经变得无法完成,而我也必须要去做其他事情了。 放弃 Simerion 对我来说的确是个困难的决定,但是我觉得在 5 年以后,是时候去干其他事情了。 开发 Simerion 的经历来说对我非常有帮助. 我学到了非常多也认识了很多人。 但是现在我认为如果在刚开始的时候不那么做的话应该会有更好的成功,而我也应该还是会在项目上.
不过那也是学习经历的一部分,失败是成功之母。 不管怎么说,我们那时候的确是在一个荒唐的,认为任何事情都是可以做到的项目上. 一个那样的游戏不过是无法实现的,而且完全不切实际 (除非是一个大公司).
错误的选择,太雄心勃勃的策划,错误的开发步骤,重新造轮子都是让 Simerion 无法完全的原因。 我们学到了这些错误然后我希望我们的经历能对您有用处。
转载至: https://blog.csdn.net/diablox0147/article/details/7734935
]]>这里我会分成三个步骤进行处理
前置常量
变量
流程
PS: 就上面的症状来看, 你是不是认为问题肯定处在 PB 上了, 真是 Naive 啊, 我也差点掉到坑里去了
变量
流程
PS: 症状有些不同, 此时你肯定又会去怀疑是不是操作系统的问题了呢, 还是 Native
PS: 看似是一个小问题, 坑是真的不少
下面按照我当时判断的可能性进行排序, 可能性高大放在前面
如果你认为我会安装上面我认为的可能原因去解决这个 Bug 那就大错特错了
上面我认为的可能原因, 不管是按照哪条去排查无疑都是工作量巨大, 特别还是遇上了前人留下来的代码山(PB), 更是难上加难
俗话说得好堵不如疏, 但是在软件行业恰恰是反其道而行的, 常常是能堵上漏洞就已是大功一件了(虽然我现在也打算这么干, 但是我仍然觉得这是一件很操蛋的事)
这里先不去管可能原因, 仅针对表象进行处理
由上得到可用信息如下:
方案: 找出所用用到屏幕长和宽的代码, 替换为
|
|
找到宽和高的宏定义 -> 替换代码 -> 重新编译 -> 运行
可以横屏, 但布局为竖屏
, 这个是致命问题首先, 这个方案是失败, 但是并不妨碍我认为这个方案是可行的
其次假设如果 有第 2 条这个致命问题的话, 或许我真的会把所用用到屏幕长和宽的代码, 都替换一遍
以上均失败(能成功就有鬼了)
能想到的黑招都使用了, 这下就只能老老实实的按照可能原因去排查了, 但不要以为我会按照上面的顺序去排查
首先按照苹果的标准来做, 要对页面进行旋转, 就必须在页面中实现shouldAutorotate
和supportedInterfaceOrientations
这两个方法, shouldAutorotate
设置为true
, supportedInterfaceOrientations
设置为需要旋转到的方向, 横屏UIInterfaceOrientationMaskLandscapeRight
, 竖屏UIInterfaceOrientationMaskPortrait
|
|
打开 Find Navigator 输入 shouldAutorotate
, supportedInterfaceOrientations
疯狂 Next
一通操作猛如虎, 一看结果 0/5
按照上图中列出的流程, 根据ViewController
的生命周期, 查询是否有代码影响到了屏幕的旋转
这里不卖关子了, 问题找到了, 我大致的罗列一下关键的代码
PA:
|
|
PB:
|
|
通过上面列出的关键代码, 想必大家已经能看出问题处在那里了
看到上面的代码瞬间就茅塞顿开(至少大部分的问题已经能解释通了), 万恶的黑魔法, 老是有人使用一些奇技淫巧来解决问题(这里我并不是要抨击这种行为, 其实我也经常会这么干, 但是还是那句话我并不喜欢, 这个到最后再来谈谈我的看法)
PA 进入 PB -> PB 调用 interfaceOrientation:
这时还是正常的, 尽管这里使用了黑魔法
PB 返回 PA -> 页面显示正常
其实在这里就已经出现问题了, 通过黑魔法改变的值并没有改回来, 当再次进入 PB, 调用interfaceOrientation:
, 黑魔法失效了(我猜测的原因是重复设置同一个值时, 无法触发系统判断此时应该需要进行旋转操作了, 这个没有找到可以验证的方法)
上面的原因都可以通过使用了黑魔法这个原因来解释吗?(没有检验的方法)
我在这里持保留意见
上面写了这么多, 看似没什么用, 其实暴露出了很多的问题
在我个人来看其实不应存在这种问题, 如果每个人都按照标准流程去处理, 无论是程序员之间的代码通讯还是代码和系统底层进行通讯, 出离奇问题的概率应该会大大减少, 即使出现了网上也大概率能找到相似的问题
那么程序员们到底是为什么要如此做呢? 难道他们就不清楚会导致这个结果吗?
有太多问题需要解释, 但又无从解释, 不过我还是想尝试强行解释一下, 对不对就随他去了
苹果的软件质量下滑, 想必大家都是有目共睹的, 下面我就随便举一些我遇到过的例子(只讲开发中的问题, 苹果的系统应用问题这里不聊)
上面我仅仅是列举除了一些影响正常开发的问题, 一些不影响开发的小问题简直懒得说了
这些我想足够能解释为什么我会认为是系统的问题了吧
当然这并不是要把一切问题都甩给系统, 而是当系统层经常性不稳定时, 开发者就会自然而然的对系统产生不信任感, 从而影响到开发者自身对问题的判断, 以至于被引导到一条死胡同里
试想一下, 当一个不懂互设计, 甚至连UIKit
都没听说个的人, 设计出来的页面, 然后再交给一个三流的程序员去开发(没有贬低的意思, 我也是三流程序员), 结果可想而知
问题不在于是否存在问题, 而是在于整个生产环境就是不健康的, 导致整条流水线上的人都很浮躁, 只想着守着自己的一亩三分地, 不愿改变也不想改变, 最后的结果就是, 先来的坑后来的, 后来的继续坑下去, 进入死循环
以至于愿意改变和学习的人也被大环境的洪流给拉下了水, 比如你去接手前辈们留下来的代码(垃圾山), 你会如何做呢, 是重写(重构几无可能, 能进行重构的代码表示还是有大致的设计的), 还是继续往垃圾山中倾倒垃圾呢? 我想答案不言自明了
至于什么代码写的好就随时都能被替代这种说法我就不想说什么了, 随它去了
业界从传统软件开发迈向现代软件开发过程中, 疯狂的强调这Change
, Runing
, More
我不能说这是错的, 其实我反倒是赞同现在软件开发模式的, 但是并不能因为我赞同就不去讨论这里面存在的问题了
当敏捷软件开发大行其道的时候, 效率虽然得到了提升, 各种新的特性被加入到了软件中, 这是一件好事, 但是伴随着这些明显的好处带来的确实软件的质量的不断的下滑
比如什么不影响使用的 Bug 可以暂时性的忽略, 先上线, 但是最后的结果就变成了, 这个 Bug 伴随着软件走向了终结都没能去解决(并不是没能力解决), 新特性却是一大堆, 而新的特性随之又带来了新的 Bug 以至于把开发者带入了 Bug 泥潭, 最终逼迫开发人员用脚投票放弃高质量代码
再次重申一下我是赞同现代软件开发的, 当然我现在也不知道这算不上是一个问题, 因为这完全可以通过人治的方式解决, 比如工作量的精准量化(尽量小的安排工作量, 0.5 天一个迭代), Bug 过多, 可以安排一整个大的迭代周期进行修复, 代码优化重构可以一定的周期进行一次, 或者每个大周期进行一次, 总归来说还是人治的成分在里面, 需要管理人员有很强的业务能力(了解开发和产品细节, 了解开发人员的能力, 能够把握开发的节奏), 在小型的团队中很难找到这样的人来总览全局, 小型团队中更多的是突击型的开发人员, 如何让这种类型的开发者参与到敏捷开发总来, 而且能够高产出和高质量代码都兼顾还不能对管理人员的要求太高
要找出这种情况适用的方案的解, 虽然有点强人所难, 但是我觉得真是因为有困难我们才更应该去解决, 毕竟程序员就是一群专门解决问题的人, 不是吗?
无论软件行业如何变化, 作为程序员的我们应该做也必须做的就不断的学习和思考, 并努力写出优秀的代码去影响后来者, 实现正循环
与君共勉
弃就一个坑
, 单这不妨碍我对这个项目做一个称重的总结, 对就是挺沉重的
依稀记得应该是
2016
, 印象笔记更改了收费模式, 每个账号只能绑定两个设备同步数据
本来这应该是件很正常不过的事, 确实轻度用户使用影响笔记免费版实在是太爽了, 根本就没必要付费, 但是他偏偏要美其名曰的说, 绝大多数用户都只有两个设备, 这就让人很恼火了, 搞得全是用户的错, 难道设备数超过了两个, 就直接砸了吗? 想盈利就明说就是了, 根本就没必有拐着弯骂人
当时想的是找替代品并不是要自己开发, 有几个有这个闲心情的呢, 做出来没人关注, 浪费时间
我心中的完美解决方案:
首先肯定是直接看一些开源的解决方案
我第一个想到的就是使用
git
来作为存储库, 这样可以依托github
来存储数据了(简直天才), 而且恰好就有一个这样的跨平台的git
客户端库libgit2
(简直完美)
最开始我的打算是分平台去实现相关的业务逻辑, 就我一个人搞, 业务量巨大
该死的我又不死心的去搞挺跨平台(博主之前就是搞跨平台开发的, 从此对跨平台深恶痛绝)
最后我闲着了Qt
跨平台开发, 当时我想的就是,Qt
毕竟是Cpp
库, 怎么滴性能也不会太差吧, 而且能支持全平台, 这个完美符合我的要求(后来事实证明我还是太年轻了, 被坑的还是不够惨, 先按下不表)
自从中了
Qt
的毒后我就一心扑到Qt
上了, 最后采用了v-play
的移动端解决方案, 刚开始看了他们几个demo应用后还沾沾自喜, 这东西还真挺不错的, 居然还能内嵌cocos2d-x
我其中的一个方案是用cocos2d-x
来做跨平台开发, 博主这时大喜, 心想如果这个不行还能使用cocos2d-x
来开发呢(呵呵…天真)
写作问题
, 读作SB
, 也就不怕别人笑话了, 奏这样了
关于这个库折腾了还几次, 整个大概经历了一两年: https://huyaohui.com/tags/libgit2/
前后经历了两次不同方式的编译, 当时我还沾沾自喜, 以为找到了正确的编译姿势
呵呵…现在看来其实两种编译方式根本没什么区别
先声明一下, 上面我写的这些都是在放屁, 其实根本不是这么回事
原因简单的要死, 就是因为我没有开启线程, 说到底还是没有仔细阅读文档
也不知道怎么原因官方 api 中没提供直接 push 的方法
尽管网上有人写了 push 功能的代码, 但是尽管尝试后, 均已失败告终(这是在当时那个时间节点的事, 在我暂停编码后, 有人开源了一些基于 libgit2 的 cpp 封装实现)
最后在 libgit2 的作者拒绝掉的 PR 中找到了某人写的示例代码, 目前使用的还是这份代码, 当你看到这份代码是别拒绝的就应该知道其实写的不是很好, 仅仅就是能用而已
就在我放弃这个项目之前, 我还尝试使用 cpp 封装的 libgit2 来解决 push 问题(尽管这份代码仍然存在问题, 但是问题已经不大了), 说再多也没啥diao用了, 弃坑了
个个不同的平台之间的表现不同, 坑的一批
虽然应该遵循, 不同平台按照不同的表现走, 但是也不能太丑了吧, 简直可以用丑到爆炸来形容了, 特别是在 Linux 平台上
最后不得不用 QSS 来处理平台表现问题, 而且在不同平台还存在兼容性的问题, 简直没法活了
由于新版本的 Qt 不再支持
webview
所以只能用WebEngine
偏偏WebEngin
又大的一p, 最终权衡选择了使用TextBrowser
来实现
坑爹开始了, 这玩意不支持CSS
,JavaScript
和一些html
的基本特性
当然这也不怪他, 设计出来就不是为了干这些事的
由上面可知得自己来处理图片的显示
OK 一同操作后先把图片下载下来然后替换掉 src 的web image
到本地图片地址
难度不算大虽然性能差点勉强算是解决了, 难道你以为这就 ojbk 了吗, 还是太年轻, 接着往下看
由于
TextBrowser
不支持相对布局属性, 比如:width="80%"
这样的写法
我不得不在插入图片的时候动态去计算图片的显示范围, 否则图片很大的话就会显示非常难看, 而且会超出显示框的显示大小, 继续 ojbk 你以为又搞定了, 仍然是图样
图片大小不能使用相对大小的直接问题就是当你窗口大小改变后, 你就必须要重新计算一遍大小, 否则还是会出现之前一样的问题, 但是重新计算有会有新的问题, 如果你文档中的图片数量过多的话计算起来就会非常慢, 特别是在window
平台上提别明显
随之有得在window
平台上加多一个机制, 就是在拖动更改窗口大小时, 只有在松开鼠标时才重新计算, 体验略差, 但是勉强就不卡了
先来说下
v-play
是个什么玩意儿, 这玩意是基于Qt Quick
的一个移动端游戏解决方案, 同时还能做界面开发, 因为它实现了一些UI
开发中常用的空间, 使用起来还是不错的, 当然这也是一个坑而已
为什么说这玩意是个坑呢, 性能是真的差, 别看他们demo
写的是挺不错的实际你用起来之后会发现是真的卡, 如果是做一些轻量级的展示类型的项目我想还是可以用的, 如果交互性太强了, 那就垃圾吧倒吧
其实如果是轻量级的展示类型的项目那么为什么不用RN
呢, 这东西连Qt
官方都不怎么上心去完善(当然我指的是QtQuick
,v-play
做的够好了)
这里顺带一提, 期间我有段时间想用这东西来搞跨平台开发(博主之前就是用这东西混了一段时间饭吃), 现在想想还好没用, 要不然不知道又要遇到多少坑呢
说到为什么弃坑这就不得不说说自己的经历了, 就在我决定开发这个项目之前, 我参加并主导了一个创业项目, 当然是以失败告终的, 要不然我也不会开始这个项目的, 毕竟这个东西肯定是赚不到钱的
那么又为什么要做呢, 这就要说到我性格了, 想做的事就一定会想着去做, 就像本项目一样, 虽然16年就想做了, 但是还是等到了17年才开始做, 期间做了大量的调研和准备工作, 如果不是创业失败也许不知道什么时候会开始这个项目
搞了半天还是没有说为什么弃坑呢, 这就来了
经历过创业失败和开发该项目遇到的问题的双重打击下, 我到是想明白了很多事, 人也通透多了
任何事情都不是做了就一定要等待一个结果的, 其实人生中有很多事是没有什么所谓的结果
或许弃坑也是一种结果呢, 是吧
迈出这一步虽然不容易, 但是决定迈出去后, 反而人清爽了许多, 每个人的精力和能力都是有局限性的
没有为自己辩解的意思, 随着年龄的增长, 对事物看法逐渐的趋于平和了, 对事物的看法反而更透彻了, 不再盲目的跟风和特立独行(表面上的)了
想说的很多, 但文笔有限, 那么就这样了吧
无论做什么都是要有一个契机的, 我弃坑这件事也是如此
事情是这样的, 平常我是会关注一些微博上的技术博主的, 他们经常会推荐一些比较好用的框架和应用什么的
就在前段时间(据我动手写这篇博文已经过去很久了), 看到今天我要说的这个项目
tamlok/vnote这个也是用Qt
开发的先来提取几个关键字
- 项目起始时间: 2017年
- star: 4455(写到这里去看了一下, 应该比我当时看到要多出不少)
- Qt
- markdown
- WebEngine
从中看出来和我这个项目很相似, 但是选型上就有很大不同了, 我选择的是纯cpp开发, 而该作者这是完全是依托于 WebEngine
先来说说各自的优缺点
我的方案的好处就是性能高, 包体小, 缺点可用的轮子少, 集成难度大
vnote 则和我的方案恰好相反, 好处是轮子多, 集成难度小, 缺点就是性能相对略差, 包体大
其实开始我也想过使用 vnote 这样方案, 但是性能差这点实在是不能忍的虽然我的markdown preview
这块的逻辑性能也比较差, 但是优化一下还是可以的, 而且我正在尝试用zserge/webview
该库的解决方案来处理我这块的逻辑, 即解决的性能问题, 也解决了包体大小的问题, 但是一切都完了, 我弃坑了
能获得这么多的 star, 肯定比我牛, 我相信 vnote 的作者也是能想到我想到的这些问题的, 至于他是什么想法我到是也不想去探寻了
这里顺便一提, 为知笔记用的也是用的 Qt WebEngine
]]>如果有人说是我垃圾不会用
Qt
的话, 我承认我确实用不好
好了这次就真的这样, 没什么要说的了
CI/CD
with Jenkins
折腾良久用于还是给我搞掂了, 下面就来说说我到底是怎么淌过这些坑的(真心就是不停的淌坑, 简直毫无乐趣可言, 不过淌完后还是很畅快的:-D)
这里不得不先来说下步骤, 因为每一步都非常重要, 稍不注意都会 GG
蒲公英
再次温馨提示: 请务必按照步骤来
本来这里没什么好说的一个命令搞定的事
但是众所周知 Oracle 开始对 Java 收费, 现在安装 Java 会出现一些麻烦, 不过可以到网上找到一些镜像
注意: 这里最好安装 OracleJava8
, 我尝试了 OpenJava12
并不行
Java 安装好后这里基本就没啥好说的了, 到官网下载对应平台的的二进制文件安装, 无脑 next 就行
使用homebrew
进行安装
|
|
注意: 按理说二进制安装后Jenkins
的服务会自动启动并打开, 但是我遇到了他自己没有启动的情况, 这里就需要自己检查一下端口占用的问题了, 我的情况是8080
端口被我安装的Docker
给占用了, 这时可以关闭Docker
或者更改Jenkins
的端口, 更改方式我这里就不给出了, 可以自行搜索
奏是按照提示无脑下一步
第一次进入会告诉你需要输入密码, 按照提示打开改路径下的文件密码就在里面, 填入就行
记住需要使用sudo
才能打开, 否则没权限(当然密码在你启动Jenkins
的时候其实就已经显示在控制台了, 仔细查看还是能找到的)
|
|
选择安装建议插件, 我这个用到的就只有shell
和git
建议创建一个新的账号, 如果你奏是像用root
账号登录的话, 那么我告诉你密码就是initialAdminPassword
里的那个长串
请看我的另一篇Blog
: SSH keys 公钥配置与使用
github
公钥配置上文中已给出, 其他平台的配置方法大同小异, 这里不赘述,可执行搜索
如果是在你自己的开发机上进行编译的话, 那你可能就不会遇到这些问题, 不过最好还是看下以供参考
上面是我的系统和编译环境
查看Xcode 链接真机调试这篇文章中关于证书相关的内容, 安装证书
当然你可以把你开发机上的证书拷贝到你的编译机上去, 同样的我这里不做阐述, 执行搜索
为什么说这里是神坑呢, 如果你不用Xcode
先进行一次编译归档操作就直接进行命令行打包就会出现找不到provisioning profile
文件, 打包失败
点击Product
-> Archive
等待编译完成后进入归档页面, 此时选择Ad Hoc
, 然后无脑下一步
最后点击Export
导出, 然后找到导出的问题件夹内的ExportOptions.plist
文件保存好, 之后使用命令行编译时需要用到(这里很重要, 网上一大堆资料都没说怎么得到这个文件, 以及这个文件的作用)
网上的资料全部都是选择第一项App Store
, 但是我选择使玩这项后, 使用命令行打包出来的ipa
并不能成功安装到手机上
这里把编译先搞清楚, 之后再来部署Jenkins
就水到渠成了, 所以我把命令行编译这块单独拿出来解释说明Jenkins
最终要执行的就是这些命令
|
|
如果上述操作都没有问题的话, ipa
安装包应该会生成在项目根目录下的build
文件夹下
上传到fir.im
应该也是大同小异的, 大家执行搜索一下, 我这里只以蒲公英为例子
|
|
上传成功, 命令行会返回code: 0
, 表示成功
如果需要上传到自己的服务器, 其实也是同理, 使用上面脚本的之后一行的curl
命令就好, 具体可自行搜索curl
如何使用
上面的所有环节都打通了, 使用Jenkins
完成自动化, 还会有什么难度吗?
不过我这里还是简单的说一下把, 免得大家看了网上的一些资料后又走上了歪路
新建任务 -> 输入任务名称 -> 构建一个自由风格的软件
源码管理 -> Git -> Repository URL -> 输入创库地址
添加 clone 创库的凭证
私钥来自之前我们创建的ssh key
, 私钥路径~/.ssh/id_rsa
注意: 公钥必须首先配置到了git
托管平台, 公钥路径~/.ssh/id_rsa.pub
首先增加构建步骤
拷贝上文的脚本至命令输入框内, 注意替换你实际的项目名称, 人后点击保存, 最后点击立即构建
, 开始执行自动化构建
此时大功告成, 如果你想使用构建触发器
的话, 请执行查阅资料, 这个比较简单, 这里就不做说明了
最开始也是使用的这种方式进行的部署, 但是很遗憾无论我怎么折腾都没能完成工作
遇到的问题: userString = "Your session has expired. Please log in.";
网友的说法是, 只要Xcode
重新登录就能解决问题了
还有的说法是没有解锁钥匙串
, 我个人任务这个说法比较可信, 但是很遗憾按照网友的说法并没能解决问题
如果哪位大佬能看到这篇文章的话, 知道这是怎么回事, 劳烦我说一声, 🙏
https://forums.developer.apple.com/thread/70326
https://medium.com/xcblog/xcodebuild-deploy-ios-app-from-command-line-c6defff0d8b8
https://shashikantjagtap.net/full-stack-ios-continuous-delivery-with-xcodebuild-and-exportoptions-plist/
http://oriochan.com/projectManage.html
https://www.jianshu.com/p/91e8f571fc2b
https://www.jianshu.com/p/9cb3d8c8c78d
https://www.jianshu.com/p/ce36997919b4
大家执行搜索安装
|
|
AndroidMaifest.xml
文件package="xxx.xxx.xxx"
xx.xxx.xxx
对应的就是包名com.huyaohui.test
com.huyaohui.test2
Lcom/huyaohui/test
Lcom/huyaohui/test2
com/huyaohui/test
文件夹com/huyaohui/test2
更改完包名的 App 重新打包
|
|
打好的包存放路径为 package-name/dist
|
|
|
|
签名完成后, 修改包名的工作就彻底结束了
没啥好总结的, 谁叫 java 那么好反编译呢 :-D
https://www.jianshu.com/p/fe56575d3991
https://www.cnblogs.com/ayanmw/p/3720804.html
TypeScript 是微软开发的 JavaScript 的超集,TypeScript 兼容 JavaScript,可以载入 JavaScript 代码然后运行。TypeScript 与 JavaScript 相比进步的地方 包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销;增加一个完整的类结构,使之更新是传统的面向对象语言。
TypeScript 微软官方网站 http://www.typescriptlang.org/
TypeScript 源码 http://typescript.codeplex.com
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
当我们把 animalName 改为 private
|
|
|
|
|
|
|
|
|
|
|
|
作用:1. 防止命名空间冲突;2. 将一个功能模块很容易的划分到不同文件中,更容易维护;
|
|
|
|
|
|
|
|
为了增强可读性,给参数 x、y 具有实际的意义,可以这样写
|
|
第二部分 number 是一个返回类型,如果无需返回类型,请使用’void’
第三部分的 function 参数类型,根据上下文类型进行推断,可以省略
|
|
|
|
|
|
例如在 C# 中,方法参数定义使用 param int[], 调用方法时,就可以传递多个 int 类型的参数
在 TypeScript 中
|
|
|
|
调用发现 getName 中的 this 关键字指向的是 getName, 访问不到外部的 name 属性
所以我们修改为:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
]]>转载请注明出处:http://www.cnblogs.com/xcong/p/4570235.html
文中代码引用自 Ts 官网和极客学院
TS 文件下载 http://files.cnblogs.com/files/xcong/TsDemo.zip
在标准 C++ 以前,都是用
#include<iostream.h>
这样的写法的,因为要包含进来的头文件名就是iostream.h
。
标准 C++ 引入了名字空间的概念,并把iostream
等标准库中的东东封装到了std
名字空间中,
同时为了不与原来的头文件混淆,规定标准 C++ 使用一套新的头文件,这套头文件的文件名后不加.h
扩展名,如iostream
、string
等等,
并且把原来 C 标准库的头文件也重新命名,如原来的string.h
就改成cstring
(就是把.h
去掉,前面加上字母c
),所以头文件包含的写法也就变成了#include <iostream>
。
并不是写了#include<iostream>
就必须用using namespace std
; 我们通常这样的写的原因是为了一下子把 std 名字空间的东东全部暴露到全局域中(就像是直接包含了 iostream.h 这种没有名字空间的头文件一样),使标准 C++ 库用起来与传统的iostream.h
一样方便。
如果不用using namespace std
, 使用标准库时就得时时带上名字空间的全名,如std::cout << "hello" << std::endl
;
#include "iostream"
与 #include<iostream>
的区别前者先在当前目录找
iostream
文件,找不到再去系统头文件路径找,后者反之。
因此,做为一个良好的习惯,在包含系统头文件时尽量用<>
,而在包含自己的工程中的头文件时用""
。
且<iostream>
和<iostream.h>
文件是不一样(前者没有后缀),实际上,在编译器#include
文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。
后缀为.h
的头文件 C++ 标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h
后缀的头文件里,c++ 标准为了和 C 区别开,也为了正确使用命名空间,规定头文件不使用后缀.h
。
因此,当使用<iostream.h>
时,相当于在 C 中调用库函数,使用的是全局命名空间,也就是早期的 C++ 实现;
当使用<iostream>
的时候,该头文件没有定义全局命名空间,必须使用namespace std
;这样才能正确使用cout
。
以上内容均来自网上,暂未查到原始出处,知道出处的请联系我加上
]]>这里我不会讲外键约束
是什么, 其他地方可以找到很多资料
这里只关注当前需要解决的问题
|
|
当插入数据时, 就会看到如下错误
|
|
根据外键约束的原理, 可以知道这就是一个先有鸡还是先有蛋的问题
因为 在插入数据时会检查parent_uuid
中的值是否存在uuid
列中, 如果不存在则插入失败
如果你的uuid
是主键的并且在插入数据时满足以下两个条件的任意一个可以插入成功
parent_uuid
为NULL
parent_uuid
等于uuid
而实际情况则是多变的, 这样处理的话制约性太强了, 方案并不好
Ps: 虽然我当前的这个项目用的是在代码中递归实现的, 但是我还是要说下下面的这个方案
之后会改成这个方案😁
同样的在这里我不会叫触发器
是什么, 资料其他地方很多
数据表还是上面的那个结构
下面是数据表中的数据(Ps: 因为是触发器实现所以不存在参入检查的问题)
id | uuid | parent_uuid |
---|---|---|
1 | a | b |
2 | b | c |
3 | c | d |
下面是触发器Sql:
|
|
下面是删除删除数据Sql:
|
|
如果不开启的话, 触发器只会触发一次, 那么最终id=3
这条记录仍然是会存在的, 这样效果级联删除的效果就没有达到了
但是 Sqlite3 默认是关闭 recursive_triggers 这个选项的, 需要手动开启, 具体的可以看下面参考资料的官方文档
你有没有遇到过以下问题:
看完这篇文章后,这些问题将会全部消失。
色彩空间,色域。我不会在这里详细介绍, 进一步了解可以阅读: http://www.dpbestflow.org/color/color-space-and-color-profiles 在那里, 你可以找到关于色彩模型, 色彩空间和色彩配置的定义, 以获得更深的理解。我也会稍后再博客中翻译整篇文章。
色彩配置。我不会在这里详细介绍, 进一步了解可以阅读: http://www.dpbestflow.org/color/color-space-and-color-profiles 在那里, 你可以找到关于色彩模型, 色彩空间和色彩配置的定义, 以获得更深的理解。我也会稍后再博客中翻译整篇文章。
苹果认为相同的 RGB 值在任何地方都应该显示相同。其实应用一个 RGB 的组合在不同的设备上不会总是有着相同的颜色,这取决于你使用的是哪一个颜色配置(color profile)。
需要知道是,color profile 是展现一个色彩空间中颜色的数值模型 (色彩空间, 色域(color space)是展现颜色的一个方式, 例如: RGB, CMYK, HSV, 等等),一些色彩配置(color profile)是“设备相关” 的,一些色彩配置(color profile)是 “设备无关” 的。这意味着同样的颜色在不同的设备 (“设备无关”) 上会展现相同的颜色,其中一些将会根据设备的特点改变颜色(“设备相关”)
同样有趣的是, 当你截图时, 不仅每个像素的 RGB 值都得到了存储, 而且还有关于被截取的设备的互补信息。这样, 苹果可以通过计算不同的 RGB 组合来使颜色在不同的设备中看起来相同, 以最佳的方式使这些颜色与设备的特性和限制相匹配。
说了这么多, 给定一个色彩空间(color space) (例如, RGB), 您将在其中有多个色彩配置 (一般 RGB、Adobe RGB、PAL/SECAM 等), 因此你将有多种方法使用不同的 RGB 组合来获得相同的颜色。
Xcode 中的 RGB 色彩配置(color profile)有 Adobe RGB、Apple RGB、Device RGB(设备 RGB)、Generic RGB(通用 RGB)、Wide Gamut RGB(广域 RGB)。要查看整个色彩配置列表, 可以从 Xcode 的 Interface Builder 的色彩选择工具中查看。
因此, 当您选择选择了 RGB 值组合,并更改了要使用的色彩配置时, 您将获得相同的颜色,但是会得出不同的 RGB 值,这是应用颜色时使开发人员非常恼火的主要问题。
例如, 通用 RGB (10、80、105) 和设备 RGB (0、99、124) 是相同的颜色,但有不同的 RGB 值。如你所见。这就是为什么最终可以有不同的颜色, 即使你使用的是你从别处取色的精确的 RGB 组合 (Photoshop 吸管, 数码测色计(Digital Color Meter)等)。
同样, 如果在不同的色彩配置中使用相同的 RGB 组合, 则会得到不同的颜色。
此外, Photoshop 处理颜色时,使用 Photoshop 的人与使用 RGB 值的人不是同一台设备不说,Adobe 使用人员 Photoshop 的色彩空间也未必与开发人员一致 (可能会是 Adobe 发明的颜色空间), 所以当你试图直接从 Photoshop 中选取它们并将其应用于 Xcode 时, 情况可能会变得更糟。
Photoshop 在编辑菜单的颜色设置中可以查看更改色彩配置。
所有这一切都解释了为什么当你使用数码测色计选择一个颜色, 你可能会得到不同的 RGB 值但是是相同的颜色。如果你在不同的显示器 (因为额外的设备信息可以添加到每个像素, 当你选择颜色, 取决于您使用的是与设备相关的色彩配置还是与设备无关的。
你可以猜到,我们要使用一个设备无关的, 如 sRGB (代表标准的 RGB), 所以无论我们将在什么地方显示,我们将得到相同的 RGB 值时。
iOS 中代码自定义设置 RGB 与 Interface Builder 自定义设置 RGB 颜色一致。都使用了 sRGB 色彩配置。
选择颜色后使用 sublime 等文本编辑器直接打开 storyboard 或者 xib 文件。即可看到如下几种结果。
如果色彩配置采用了 sRGB ,sb/xib 的 xml 中会这样写,colorSpace=”custom” customColorSpace=”sRGB” ,一般都是使用颜色选择器自定义 RGB 后自动选择的 sRGB 色彩配置
如果色彩配置采用了 Display P3, sb/xib 的 xml 中会这样写,colorSpace=”custom” customColorSpace=”displayP3”,
如果色彩配置采用了 Generic RGB, sb/xib 的 xml 中会这样写,colorSpace=”calibratedRGB” ,一般都是使用颜色选择器自定义 RGB 后手动选择的 Generic RGB 色彩配置
如果色彩配置采用了 Generic Gray , sb/xib 的 xml 中会这样写,colorSpace=”calibratedWhite”,一般都是系统默认的灰白颜色。
如果色彩配置采用了 Device Gray , sb/xib 的 xml 中会这样写,colorSpace=”deviceRGB”,,一般都是使用颜色选择器自定义 RGB 后手动选择的 DeviceRGB 色彩配置
如果色彩配置采用了 Adobe RGB , sb/xib 的 xml 中会这样写,colorSpace=”adobeRGB1998”,,一般都是使用颜色选择器自定义 RGB 后手动选择的 Adobe RGB 色彩配置
最后发现,手动选择的 Apple RGB 色彩配置,sb/xib 的 xml 中会这样写,colorSpace=”sRGB”,说明苹果默认统一成了 sRGB.
最新 Xcode8 测试, 以下代码使用 sRGB 色彩配置
|
|
类似 [UIColor darkGrayColor] 使用 Generic Gray 色彩配置
不信我们 log 下
|
|
既然你明白了这件事的 ‘原因’, 让我们来谈谈技巧。
要选择颜色, 我们将使用苹果系统自带的 “数码测色计” 应用,你可以在系统的实用工具或者 Spotlight 找到。这个程序适用于一些色彩配置计算屏幕上的一个像素的 RGB 值。
Xcode 的颜色选择器也支持拾取,但是不能设置指定色彩配置的颜色拾取。只能是 sRGB(代表标准的 RGB)。
要应用颜色,我们将使用 Xcode 的 Interface Builder, 或者, 我们也可以以代码方式进行。
打开数码测色计(Digital Color Meter),下拉中选择 “以 sRGB 显示”/ “Display in sRGB”, 使用取色吸管取色,shift+cmd+c,快捷键拷贝当前屏幕像素 RGB 的值.
如果想拷贝 16 进制颜色,在菜单的显示中可以设置切换。
然后再 Xcode 的 Interface Builder,颜色选择器中选择 sRGB(默认也是 sRGB),填入取的颜色 RGB 值。
或者代码设置
|
|
所以,总结下技巧:
使用取色软件,以 sRGB 色彩配置,取得 RGB 值。
设计最好也使用 sRGB 色彩配置进行设计。
如果设计要给开发取色要使用 sRGB 取色。
因为代码初始化 UIColor 是不能像 NSColor 一样指定色彩空间的。所以要以不变(sRGB)应万变。
使用 sRGB 在 Xcode Interface Builder(或通过代码) 中应用这些 RGB 值。
https://stackoverflow.com/questions/28367811/color-in-storyboard-not-matching-uicolor
http://www.skyfox.org/ios-app-color-set-and-color-profile.html
我们在写代码时,完全可以用空间来换取时间,比如说,要判断某某年是不是闰年,你可能会花一点心思写了一个算法,而且由于是一个算法,也就意味着,每次给一个年份,都是要通过计算得到是否是闰年的结果。还有另一个办法就是,事先建立一个有 2 050 个元素的数组(年数略比现实多一点),然后把所有的年份按下标的数字对应,如果是闰年,此数组项的值就是 1,如果不是值为 0。这样,所谓的判断某一年是否是闰年,就变成了查找这个数组的某一项的值是多少的问题。此时,我们的运算是最小化了,但是硬盘上或者内存中需要存储这 2050 个 0 和 1。
算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而改变。存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是 “就地 “ 进行的,是节省存储的算法,如这一节介绍过的几个算法都是如此;有的算法需要占用的临时工作单元数与解决问题的规模 n 有关,它随着 n 的增大而增大,当 n 较大时,将占用较多的存储单元,例如将快速排序和归并排序算法就属于这种情况。
通过一笔空间上的开销来换取计算时间的小技巧。到底哪一个好,其实要看你用在什么地方。
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)= O(f(n)),其中,n 为问题的规模,f(n) 为语句关于 n 所占存储空间的函数。
一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为 O(1)。
当一个算法的空间复杂度为一个常量,即不随被处理数据量 n 的大小而改变时,可表示为 O(1);当一个算法的空间复杂度与以 2 为底的 n 的对数成正比时,可表示为 0(10g2n);当一个算法的空 I 司复杂度与 n 成线性比例关系时,可表示为 0(n). 若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。
对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法 (特别是大型算法) 时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。
本文地址:http://www.nowamagic.net/librarys/veda/detail/2197,欢迎访问原出处。
]]>在进行算法分析时,语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 的变化情况并确定 T(n) 的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n}=0(f(n))。它表示随问题规模 n 的增大,算法执行时间的埔长率和 f(n) 的埔长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中 f( n) 是问题规横 n 的某个函数。
计算 1 + 2 + 3 + 4 + …… + 100。代码如下,之前也有讲过:
|
|
从代码附加的注释可以看到所有代码都执行了多少次。那么这写代码语句执行次数的总和就可以理解为是该算法计算出结果所需要的时间。该算法所用的时间(算法语句执行的总次数)为: 1 + (n + 1) + n + 1 = 2n + 3
而当 n 不断增大,比如我们这次所要计算的不是 1 + 2 + 3 + 4 + …… + 100 = ? 而是 1 + 2 + 3 + 4 + …… + n = ?其中 n 是一个十分大的数字,那么由此可见,上述算法的执行总次数(所需时间)会随着 n 的增大而增加,但是在 for 循环以外的语句并不受 n 的规模影响(永远都只执行一次)。所以我们可以将上述算法的执行总次数简单的记做: 2n 或者简记 n
这样我们就得到了我们设计的算法的时间复杂度,我们把它记作: O(n)
再来看看高斯的算法:
|
|
这个算法的时间复杂度: O(3),但一般记作 O(1)。
从感官上我们就不难看出,从算法的效率上看,O(3) < O(n) 的,所以高斯的算法更快,更优秀。
下面再来一个例子:
|
|
上面的代码严格的说不能称之为一个算法,毕竟它很 “无聊而且莫名其妙”(毕竟算法是为了解决问题而设计的嘛),先不论这个“算法” 能解决什么问题,我们看一下它的 “大 O 阶” 如何推导,还是先计算一下它的执行总次数:
执行总次数 = 1 + (n + 1) + n(n + 1) + nn + (n + 1) + 1 = 2n2 + 3n + 3
如何推导大 o 阶呢?我们给出了下面 的推导方法:
按照上面推导 “大 O 阶” 的步骤我们先来第一步:“用常数 1 取代运行时间中的所有加法常数”,则上面的算式变为:执行总次数 = 2n^2 + 3n + 1
第二步:“在修改后的运行次数函数中,只保留最高阶项”。这里的最高阶是 n 的二次方,所以算式变为:执行总次数 = 2n^2
第三步:“如果最高阶项存在且不是 1 ,则去除与这个项相乘的常数”。这里 n 的二次方不是 1 所以要去除这个项的相乘常数,算式变为:执行总次数 = n^2
因此最后我们得到上面那段代码的算法时间复杂度表示为: O(n^2)
O(1) 常数阶 < O(logn) 对数阶 < O(n) 线性阶 < O(nlogn) < O(n^2) 平方阶 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }
本文地址:http://www.nowamagic.net/librarys/veda/detail/2195,欢迎访问原出处。
]]>当涉及到ios应用程序的开发时, 模型视图控制器是一种常见的设计模式.
通常视图层由 UIKit 中的元素组成, 这些元素通过程序或 xib 文件定义, 模型层包含应用程序的业务逻辑, 控制器层(由 UIViewController 类表示)是模型和视图之间的粘合剂.
这种模式的一个很好的部分是将业务逻辑和业务规则封装在模型层中. 但是, UIViewController 仍然包含与 UI 有关的逻辑, 这意味着如下:
承担所有这些工作, UIViewController 将会变得巨大而难以维护和测试.
所以, 现在是时候考虑改进 MVC 来处理这些问题了.
我们称之为改进 模型(Model)-视图(View)-主持人(Presenter) MVP.
MVP 模式在1996年由 Mike Potel 首次引入, 并且多年来进行了多次讨论.
在他的文章中, GUI架构 Martin Fowler 讨论了这种模式, 并将其与其他管理 UI 代码的模式进行了比较.
有很多 MVP 的变体, 它们之间有很小的差异.
在这篇文章中, 我选择了目前应用程序开发中常用的常用一种.
这个变体的特征是:
以下示例将向您展示如何在操作中使用 MVP
我们的示例是一个非常简单的应用程序, 它只显示一个简单的用户列表. 您可以从这里获得完整的源代码: https://github.com/iyadagha/iOS-mvp-sample .
让我们从用户的简单数据模型开始:
|
|
那么我们实现一个简单的用户服务, 即异步返回用户列表:
|
|
下一步是编写userpresenter.
首先我们需要用户的数据模型, 可以直接在视图中使用.
它包含根据需要从视图中正确格式化的数据:
|
|
之后, 我们需要对视图进行抽象, 这可以在 Presenter 不知道 UIViewController 的情况下使用.
我们通过定义一个协议 UserView 来做到这一点:
|
|
该协议将在 Presenter 中使用, 稍后将在 UIViewController 中实现. 基本上, 协议包含了在 Presenter 中控制 View 的函数调用.
Presenter 看起来是这样的:
|
|
我们将在后面看到 Presenter 可以通过函数attachView(view:UserView)
和attachView(view:UserView)
来更好地控制 UIViewContoller 的生命周期方法
请注意, 将User
转换为UserViewData
是 Presenter 的责任.
还要注意, userView
必须weak
以避免保留周期.
实现的最后一部分是UserViewController:
|
|
我们的 ViewController 有一个 tableView 来显示用户列表、一个 emptyView (如果没有用户时显示)和一个当应用程序正在加载用户时显示的 activityIndicator. 此外, 它还有一个 userPresenter 和一个用户列表.
在viewDidLoad方
法中, UserViewController将自己连接到 Presenter.
这是可行的, 因为我们很快就会看到 UserViewController 实现了 UserView 协议.
|
|
正如我们所看到的, 这些函数不包含复杂的逻辑, 他们只是在进行纯视图管理.
最后, UITableViewDataSource 实现非常基本, 看起来如下:
|
|
做MVP的好处之一是能够在不测试UIViewController本身的情况下测试大部分UI逻辑.
如果我们对我们的 Presenter 有一个很好的单元测试覆盖范围, 我们就不需要为UIViewController编写单元测试了.
现在让我们看看如何测试我们的UserPresenter. 首先, 我们定义两个mock对象. 其中一个mock是UserService, 以使它提供所需的用户列表. 另一个mock是UserView, 以验证这些方法是否被正确调用.
|
|
现在, 我们可以测试当服务提供一个非空用户列表时, Presenter 的行为是否正确.
|
|
同样的, 如果服务返回一个空的用户列表, 我们也可以测试 Presenter 是否正确工作.
|
|
我们已经看到了MVP是MVC的演进. 我们只需要将UI逻辑放在一个名为 Presenter 的额外组件中, 并 被动的 (dump) 使我们的UIViewController.
MVP的特点之一是 Presenter 和 View 互相认识.
在这种情况下, 视图 UIViewController 具有对演示者的引用, 反之亦然.
尽管可以使用反应式编程来删除演示者中使用的视图的参考.
使用 ReactiveCocoa 或 RxSwift 等响应式框架, 可以构建一个体系结构, 其中只有 View 知道 Presenter, 反之亦然.
在这种情况下, 架构将被称为 MVVM.
如果你想在iOS中了解更多关于MVVM的信息, 请查看以下帖子:
MVVM Tutorial with ReactiveCocoa
Implementing MVVM in iOS with RxSwift
首先你需要安装一个干净的操作系统, 使用虚拟机就好了, 保持你的虚拟机和你开发的电脑在同一个网段内(Ps: 方便传输数据)
如何安装虚拟机这里就不细说了, 网上搜索一大堆资料.
Frameworks
目录里, 直到程序可运行不同平台之间的一些细微差别
macOS 平台如果出现问题, 会弹出一个崩溃框, 点击报告
按钮, 可以看到崩溃日志, 里面会告诉你除了什么问题, 按照提示操作即可
当你的Frameworks
目录下确实存在这个运行库的时候, 还是爆类似如下的错误时, 可检查是否是链接库的链接位置有问题
|
|
|
|
通过上面可知, 确实是链接位置有问题, 应该是一个相对路径才对
|
|
Linux 平台需要使用终端运行可执行程序, 因为自己双击打开可执行程序, 无法查看错误日志.
Windows 平台和 macOS 平台太类似, 只不过 Windows 平台太会直接告诉你出了什么问题
需要注意的是: 如果出现了api-ms-win-crt-runtime-l1-1-0.dll
丢失, 那么意味着客户机没有安装运行库, 安装msvc2016_x64
或者msvc2016_x32
即可, google 一下就可以查看到详情了, 这里不细说了
|
|
google 到的解决方案是需要安装pip install six
这个包
如果不卸载, 无法使用系统 python, LLDB
调用的是系统的 python 而不是自己安装的
需要注意的是, 卸载完后需要重启一下控制台
|
|
|
|
|
|
ok, 打完收工, 完美如初
不要手贱, 不要手贱, 不要手贱…
]]>QPropertyAnimation
修改window
的size
的时候, window
内的widget
会出现闪烁的现象, 当时的猜测应该是因为widget
没有参加到渲染中去, 但是就是不知道该怎么解决, 今天还真被我证实了我的猜测是正确的, 挺高兴的呢, 下面就来说下解决方案, 其实就一行代码, 现实就往往是这么残酷, o(╯□╰)o
Native Widgets vs Alien Widgets
Introduced in Qt 4.4, alien widgets are widgets unknown to the windowing system. They do not have a native window handle associated with them. This feature significantly speeds up widget painting, resizing, and removes flicker.
Should you require the old behavior with native windows, you can choose one of the following options:
QT_USE_NATIVE_WINDOWS
=1 in your environment.Qt::AA_NativeWindows
attribute on your application. All widgets will be native widgets.Qt::WA_NativeWindow
attribute on widgets: The widget itself and all of its ancestors will become native (unless Qt::WA_DontCreateNativeAncestors
is set).QWidget::winId
to enforce a native window (this implies 3).Qt::WA_PaintOnScreen
attribute to enforce a native window (this implies 3).原文地址: http://doc.qt.io/qt-5/qwidget.html
英语渣这里就不翻译了, 大概意思就是Qt 4.4
里面引用了一种机制可以消除闪烁的现象, 但是不知道为什么在5.0
之后又移除了这个功能, 只做一个选项供大家使用(Ps: 这一点上文并没有提到, 但是联想到这是Qt5
的文档应该也能想到, 但是我还是找到了有人在社区的提问, 单是没人回, 这就很微妙了->_->传送门)
上文已经说了不过我这里还是写一下, 都是我测试过的哦..
上文中提到的方法, 我只试了两个, 其他的并未尝试, 故不做说明, 见谅
作用于整个应用
|
|
作用于单个QWidget
|
|
当全局设置该属性后, 会导致QTextEdit
和QPlainTextEdit
无法输入中文
http://doc.qt.io/qt-5/qwidget.html
https://forum.qt.io/topic/22933/qt5-resize-or-animation-of-qmainwindow-causes-flicker
Qt
以来其实一直都存在这个问题没能解决(Ps: 期间尝试N个版本), 要问为什么我能忍到现在, 主要还是找到一个临时的解决方案, 通过Qss
来设置图标就不会出现失真的情况, 还有一种方案就是使用svg
格式的图片, 也能解决部分问题QLineEdit
的icon
的时候无效了.iOS
开发中有@2x
和@3x
的图片, 用来适配不同屏幕分辨率, 于是搜索qt mac 图片 两倍大小
, 真就被我找到答案了, 果然之前还是没有找到正确的搜索姿势o(╯□╰)o
The key to the OS X high-dpi mode is that most geometry that was previously specified in device pixels are now in device-independent points. This includes desktop geometry (which on the 15 inch retina MacBook Pro is 1440×900 and not the full 2880×1800), window geometry and event coordinates. The CoreGraphics paint engine is aware of the full resolution and will produce output at that resolution. For example, a 100×100 window occupies the same area on screen on a normal and high-dpi screen (everything else being equal). On the high-dpi screen the window’s backing store contains 200×200 pixels.
The main benefits of this mode is backwards compatibility and free high-dpi vector graphics. Unaware applications simply continue to work with the same geometry as before and can keep hardcoded pixel values. At the same time they get crisp vector graphics such as text for free. Raster graphics does not get an automatic improvement but is manageable. The downside is the inevitable coordinate system confusion when working with code that mixes points and pixels.
The scale factor between points and pixels is always 2x. This is also true when changing the screen resolution – points and pixels are scaled by the same amount. When scaling for “More Space” applications will render to a large backing store which is then scaled down to the physical screen resolution.
不知道怎么翻译o(╯□╰)o, 大概意思就是OS X
采用的是retina
屏幕, 这种屏幕在相同尺寸的情况下的一个像素点是之前屏幕的两倍, 也就是DPI更高
而Qt
默认并不支持这种处理方式, 所以导致实际有一个像素点对应屏幕上两个像素点, 这也就是失真的原因了
原文地址: http://blog.qt.io/blog/2013/04/25/retina-display-support-for-mac-os-ios-and-x11/
使用QPainter
手动设置绘制大小, 如下代码的图片绘制已经被修改成了可以在高DPI系统中返回一个更大的像素映射, 但是这种行为会破坏现有的代码结构, 代码来控制最好的情况应该是Qt
框架来直接控制
|
|
所以Qt
使用AA_UseHighDpiPixmaps
这个应用程序属性来控制渲染:
|
|
当没有开启搞质量DPI
时:
经过尝试, 使用svg矢量图来显示icon一切正常, 单是设置QPushButton
的icon
时无效, 设置QTreeViewItem
的icon
是正常的
还有就是貌似使用QSS设置icon也不会出现这个bug, 但是不是绝对的, 设置QLineEdit
的icon
的时候还是会模糊
对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。尽管 Ribbon 界面的出现在一定程度上减少了对话框的使用几率,但是,我们依然可以在最新版本的 Office 中发现不少对话框。因此,在可预见的未来,对话框会一直存在于我们的程序之中。
Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。
简介: 在关闭模式对话框之前,程序不能进行其他工作
|
|
下图中当Dialog
没关闭前, 无法操作MainWindow
简介: 运行在应用程序中,对于任何其他窗口都是独立窗口
|
|
下图中Dialog
和MainWindow
互不影响
简介: 在关闭模式对话框之前, 程序可以修改父窗口的大小, 并且对话框会固定停留在父窗口的标题下面
|
|
效果如下图
]]>CMake
C/CPP
混编的一些细节, 要不然会被坑的很惨, 顺便把C/CPP
混编原理和实现也说一下.
就像问题本身所说, C/C++混合编程也就是一个工程中, 在C函数中调用C++函数的方法, 在C++的函数中能够调用C函数的方法.
在我们日常开发中, 也许会遇到这么一些情况, 同事A, C非常牛逼, 但是对C++一窍不通; 同事B, C++信手拈来, 但是对C却满头雾水. 但是在工作中有这么一种需求, 同事A需要用到C++的方法, 同事B需要用到C的方法, 这怎么办?
没错, 最简单的就是, 同事A把C的代码写好, 然后同事B只管调用即可, 同理, 同事A只管调用同事B写好的C++代码, 各司其职, 提高工作效率.
那么, 这混合编程究竟要怎么实现呢?
在介绍之前, 我们先简单了解下以下几个概念
C++和Java中的函数重载的定义一致,
即在相同的作用域内, C++允许多个函数名称相同, 而形参列表不同:
|
|
然而大家有没有想过为什么C++支持函数重载, 而C却不支持函数重载呢?
这个就要涉及到C++的名字改编机制了. 请往下看~
|
|
output:
ps: 不提供test()函数的实现是让CMake
链接的时候报错, 这样我们才能看清楚test()函数的真面目!
|
|
output:
|
|
output:
|
|
output:
ps : 有的系统的编译器会编译成_test_int 这种格式, 名字改编机制只是一种思路, 并没有一种唯一的命名规范, 不同的编译器命名规范不同, 但是思路一致! 如下:
|
|
extern相信大家比较熟悉, 它一般用来声明一个函数, 全局变量的作用域. extern告诉编译器, 其声明的函数和变量可以供本文件或者其他文件使用. 这里不再赘述.
extern “C” 中的C是什么意思呢?
这里的C不是指C语言这一门语言, 而是表示一种编译和链接的规约. C表示符合C语言的编译和连接规约的任何语言,如Fortran(公式翻译)、assembler(汇编语言)等。
ps: extern “C” 只是指定编译和链接的规约, 并不会影响语义, 所以在C++文件中该怎么写还得怎么写, 必须遵循C++的语法规范.
在C++源文件的语句前加上 extern “C” 的作用就是告诉编译器, 这一段代码按照类C的编译和链接规约来编译和链接(对, 也就是按照类C的函数命名规范编译)
通过上面几个例子, 相信大家很容易就能知道为什么C++支持重载而C不支持重载了.
因为C++有名字改编机制而C没有!
所以在C中, 只要函数名相同, 不管你的形参列表如何南辕北辙, 编译器均会将其编译为同一函数名, 这样在程序执行过程中就会造成函数调用的二义性(也就是对于相同函数名的函数, 程序并不知道应该调用哪一个函数), 这是不允许的, 所以会报错.
然而对于C++而言, 尽管他们的函数名相同, 但是因为他们的形参列表不同, 编译器编译后实际上会为他们改名为不同名字的函数, 所以程序执行调用函数的时候并不会产生二义性, 因此C++允许函数重载.
这里扯一句题外话, C++的重载被认为不是多态, 因为多态是动态运行时对方法的绑定, 而C++的函数重载最多算是编译时的”多态”. (这句话不一定正确, 请大家纠正)
|
|
这里有一点需要注意当
CMake
的project(projectname LANGUAGES CXX)
方法指定了语言时,CMake
只会编译指定的语言的代码, 而导致C
语言代码不被编译, 这里需要特别注意
extern "C"
CMake
指定了语言时, 只有指定的语言才会参与编译https://stackoverflow.com/questions/4598308/how-do-i-compile-and-link-c-code-with-compiled-c-code
http://guangming008.blog.163.com/blog/static/1203968201011634426908/
http://www.jianshu.com/p/8d3eb96e142a
Qss
和css
是一样的, 父节点属性默认会被子节点继承. 不过我遇到的问题比较奇葩就是了, 以至于我一时懵逼了, 下面我就来讲讲我的遭遇/(ㄒoㄒ)/~~
在开发界面的过程中, 莫名其面的QTableWidget
的滚动条变成方块了, 然后我单独开启一个项目测试又是正常的, 感觉非常奇怪
后来我惊讶的发现只要把QTableWidget
的父节点QWidget
改成删除就好了, 我想当然的以为是QWidget
的bug, 我就把父节点QWidget
换成QFrame
, 居然可以了, 这让我更加确信是QWidget
的bug了, 也让我走进了万劫不复的深渊
然后我继续忙, 突然间QTableWidget
的滚动条又变成方块了, 遂陷入沉思, 最后决定放弃, 先把滚动条关闭再说, 不想再折腾这个东西了(已经折腾两天了/(ㄒoㄒ)/~~)
在写Qss
的时候不小心写错了个单词, 然后发现控制台出现了, 好几个相同的报错, 遂想到是不是当前节点的子节点也修改了这个属性呢? 当时还没想到是这个原因导致的滚动条bug(前面说了我已经放弃了)
再后来我不甘心继续找滚动条bug的原因, 我还怀疑是Qt
版本的问题, 于是就用一个旧版本去重新实现该功能, 发现一切正常, 我又相当然的以为真是Qt
的bug, 然后我大喜欢把项目迁移的旧版Qt
上, 又发现直接用旧版本Qt
打开.ui
文件滚动条bug还在, 于是乎我就用旧版本Qt
重新吧界面画了一遍, 一开始的时候还没问题, 但是当我改完细节后, 立马就不行了, 滚动条bug又出现了
于是乎我慢慢的撤销代码, 一步一步测试, 最后发现是设置了Qss
才导致的滚动条bug, 突然灵光一闪记起来了之前的写出单词导致控制台出现相同的报错, 一切的线索都链接起来了
终于知道为什么连google都找不答案, 原来是直接太傻逼了, 最后搜索 qt stylesheet
搜索框下面直接出现了qt stylesheet 仅限当前
, 我想我肯定找到答案了
选择器 | 实例 | 描述 |
---|---|---|
通用选择器 | * | 匹配所有的widget |
类型选择器 | QPushButton | 匹配所有的QPushButton实例和继承于它的子类 |
属性选择器 | QPushButton[flat=”false”] | 匹配所有非flat的QPushButton(通常情况下,使用Q_PROPERTY宏来声明你的属性,比如此例中的flat),并且要注意,你的属性类型要受 QVariant::toString()支持(查看toString()方法的帮助文档以获取更详细的解释). 这个选择器类型也可以用来判断动态属性,要了解更多使用自定义动态属性的细节,请参考使用自定义动态属性 。 除了使用=,你还可以使用~=来判断一个QStringList中是否包含给定的QString。 警告:如果在设置了样式表后,相应的属性值发生了改变(如:flat变成了”true”),则有必要重新加载样式表,一个有效的方法是,取消样式表,再重新设置一次,下面的代码是其中一种方式: style()->unpolish(this); style()->polish(this);// force a stylesheet recomputation |
类选择器 | .QPushButton | 匹配所有的QPushButton实例,但不包括它的子类,与*[class~=”QPushButton”]是等价的。 |
ID选择器 | QPushButton#okButton | 匹配所有object name为”okButton”的QPushButton实例。 |
后裔选择器 | QDialog QPushButton | 匹配所有继承于QDialog(包括其所有子孙)的QPushButton实例。 |
子选择器 | QDialog > QPushButton | 匹配所有直接继承与QDialog的QPushButton实例。 |
http://blog.csdn.net/qq_24571549/article/details/64131396
http://blog.csdn.net/vonger/article/details/7899101
主要原因是选择错了编译环境, 应该直接使用Qt Creator
进行编译, 而我使用的是Cmake GUI
进行的编译, 导致最后编译出来的库在Qt
中部分功能无法正常使用.
这一步非常重要, 之前我也试过直接使用Qt Creator
导入第三方库, 会导入失败, 原因就在这没有选择C编译器
(libgit2
是C语言库
)
直接选择CMakeLists.txt
打开
选择上面配置好C编译器
的构建工具包
最后构建出来的项目如上图所示, 如果没有配置C编译器
的话就只能看到一个CMakeLists.txt
文件, 然后点击运行
运行完成后, 不出意外的话会在项目同级目录下生成一个build
目录, 里面有我们要的编译后生成的二进制文件, 至此编译完成.(在windows平台如法炮制就好, linux平台暂未测试)