这个不得不吐槽,Arc 的下载体验是真的垃圾,都不能用差来说了。下面列出几点不能忍的部分:
这些问题给他们反馈过,他们让我等待后续更新,这是 6 月 19 号的事了,现在还没有任何改进。另外还有一个问题是其媒体库显示的不是通过 Arc 保存的,而是所有的。
关于窗口管理我需要先描述一下我的使用场景,我的 Arc 浏览器需要打开两个窗口:
大家应该也知道,Arc 可以通过 “Arc Traffic Control” 来管理外部连接通过 Arc 打开时使用哪一个 Profile 来打开。现在问题就出现在这里,我设置了某些规则让 Arc 打开这些从外部来的连接时在 Work 的 Profile 中打开。这个需求看上去也没什么问题吧,合情合理!但是,如果我此时如果在窗口 1,如果打开这些匹配到 Work Profile 的外部连接,正常情况下你觉得 Arc 会怎么做呢?我先说一下我的期待场景:Arc 切换到窗口 2(因为窗口 2 打开的 Work Space 正关联着 Work Profile)。然而 Arc 并没有这么做,他仍然停留在窗口 1,然后将 Space 切到 Work,然后打开一个新的标签。这个问题我也反馈了,只是收到了回复,然而也没有后续进展。
不过最近的一次更新,Arc 做了一些改动:每个窗口的所有标签都同步了,即每个窗口的标签都是一样的,不一样的是你可能在窗口 1 打开了 A 标签,此时窗口 2 也有 A 标签,但是窗口 2 的 A 标签却没有打开,点击后才会打开 A 标签的网址。
关于这个,我并不是说它不好,只是快捷键用着很不顺手。你可能也知道,macOS 的 Switcher 是 Command + TAB
,最起码这两个键用着很顺手,大拇指+无名指或者小指,相当方便操作。然而 Arc 的 Switcher 是 Ctrl + TAB
,按 Ctrl
键左下角,一般用小拇指,TAB
键一般也用小拇指按,如果 Ctrl
键用小拇指按了,TAB
键用哪根手指按?你可能会说可以改快捷键啊!是的,我可以改快捷键,但是改成什么呢?那么多顺手的快捷键都被真用了,用哪一个快捷键是一个让人头疼的问题。而且为了一个无足轻重的小功能来想一个快捷键来设置这么一问题而头疼,不值得!
这也是一个让人很讨厌的问题,Arc 的前进后退手势及不跟手!它不像 Safari 的手势,你能看到整个网页会随着你的手移动而移动。Arc 显示的是箭头,而且你要滑动一定的距离才能后退或者前进,而且还不能快速的话,滑到一定程度之后手要停顿一下,就是因为如此,我经常很会的时候要滑动很多下才能完成后退操作,有时候甚至会划过头。说实话,有这个时间我可以点前进、后退按钮了。
我之前也鼓吹过 Arc 的 Command bar 多么的棒,但是有时候用起来却并不那么顺手。这个场景可能每个人都能用到,比如说我当前打开了一个 A 网页(也可以是其他已打开或打开过的标签页),此时我调起 Command Bar 想进行搜索,关键字正包含在某个网页的标题中。Command bar 会将匹配的网页排在第一位,这会导致我回车之后会直接调到那个标签页(Switch to Tab),或者打开匹配的页面(如果没有存在于已打开的标签页中),或者啥也不做(匹配当前网页的情况下)。我觉得正确的行为应该是如果真匹配到某个标签页,Command bar 应该是自动补全匹配的标签页所有关键字,自己输入的关键字的后面应该是高亮的自动补全的内容,如果此时回车是跳到相应的标签页,如果想搜索可以继续输入或者按一下退格键再按回车。
当前的行为:
期待的行为:
Sidebar……,怎么说呢,这个感觉不能算吐槽吧。只是觉得它的存在可以说是瑕瑜互见。把他关闭的情况下可视面积虽然大了一些,但是打开 Sidebar 会占用更大空间。虽然它可以自动的显示、隐藏,但是有时候想做快捷操作的时候,又觉得很影响操作。
这个算是临时想到的,Boost 虽然方便,但是它有个缺点,就是 不能全局配置! 这也是前几天我在写除去 target="_blank"
脚本时发现的(为什么国内的大型网站都在滥用 target=“_blank”?)。我写一个脚本,想让它匹配所有的网站,他无法做到,此时我只能装一个篡改猴(我以前都叫油猴,后来发现官网对此做了说明,官网首页:首页 | Tampermonkey)篡改猴真是所有浏览器的必备插件,能将写的脚本在其他浏览器上运行,多好。
篡改猴 (Tampermonkey) 是拥有 超过 1000 万用户 的最流行的浏览器扩展之一。 它适用于 Chrome、Microsoft Edge、Safari、Opera Next 和 Firefox。
有些人也会把篡改猴(Tampermonkey)称作油猴(Greasemonkey),尽管后者只是一款仅适用于 Firefox 浏览器的浏览器扩展程序。
它允许用户自定义并增强您最喜爱的网页的功能。用户脚本是小型 JavaScript 程序,可用于向网页添加新功能或修改现有功能。使用 篡改猴,您可以轻松在任何网站上创建、管理和运行这些用户脚本。
以上都是根据我个人的喜好和使用习惯进行的总结,仅代表个人观点。总体来说 Arc 也算是一个合格的(或者说不错的)浏览器(Arc 也算是近些年来让人眼前一亮的浏览器了。),如果你打算尝试请不要因为我以上的总结打消了你尝试的念头,每个人的使用习惯和场景不同,得出来的结论也会不同。
]]>a
标签的 target="_blank"
属性,我最近试了很多个网站,包括但不仅限于 哔哩哔哩、网易、新浪网、腾讯网、今日头条等。这个是相当烦人的,我在这些网站上点击任意一个连接,全部是打开新的标签页或者窗口,浏览下来不一会就全是标签页。我在想,国内的这些人一点都不注重用户体验吗?这么害怕用户从网页跳走吗?难道不知道如果用户想在新标签页打开可以在连接上按一下鼠标滚轮吗?不知道用户想在新标签页打开可以按住 Command + 点击
吗?真的觉得很多国内的厂商都是臭不要脸,类似于像开屏广告以及各个视频平台的广告一样,玩出各种花来,为的是从用户身上挖出剩余价值,什么用户体验,都是次要的,反正你有离不开我。今天真是越想越气不过,花了点时间写了个油猴脚本来解决这个问题:
原理其实也很简单,就是在网页在加载完之后列出所有 target="_blank"
的 a
元素,判断它的 host
,如果和当前的网址是一样的 host
,就去除 target
属性。另外很多网站都是懒加载的,所以要通过 MutationObserver
来观察这些新加的 a
元素以及中途把 target
设为 _blank
的元素,用相同的逻辑将 target
属性移除掉。当时在处理网易的时候遇到了一个问题,不管怎么弄都是打开新的标签页,而且我看也没有 target="_blank"
属性啊!后来仔细观察发现,它在 head
中放入了 base
标签:<base target="_blank" />
,效果其实是一样的,还省的在每个 a
元素中添加 target="_blank"
了。解决办法也很简单粗暴,直接找到这个元素并移除。除此之外,网易还有一个恶心的问题,在 www.163.com
上的连接指向 news.163.com
,在 news.163.com
上的连接指向 www.163.com
。
刚才发现之前的脚本在头条上无法工作,研究后发现其恶心的是给 document
增加了 click
事件监听器,在监听器里强制点链接在新窗口中打开,你不仁也不能怪我不义了,只能替换你的 addEventListener
方法了。
我实在想不通他们都是怎么想的,我看完新闻、咨询、视频等,我点开另外一个连接我为什么还要保留当前的网页?留着占内存吗?不知道他们自己用不用,自己觉不觉得烦!总之以后清净了~。
]]>just 语法和 make 很类似,如果你有 make 基础的话,可以很快上手。下面简单介绍一下 just 的用法以及和 make 的一些区别(首先一个区别是 Makefile 中每个执行项叫做目标(target),而 just 中叫做配方(recipe)):
首先 just 和 make 一样,需要一个 Justfile(make 是 Makefile,just 这个文件的文件名可以是 Justfile、justfile 或者 .justfile 等),不区分大小写,可以在前面加个.
来隐藏这个文件。just 会从当前目录向上查找 justfile 文件,而 make 不会。 这就意味着只有某一个父级目录存在一个 justfile 文件,那就可以执行该 justfile 里的相关的代码。
如果 Makefile 的在当前目录下存在一个文件,这个文件和某个 target 名称相同,则这个 target 就不会执行,并且打印出错误信息:make: xxx' is up to date.
,此时你需要在 Makefile 文件中加上一样 .PHONY xxx
来防止此问题的发生,just 则没有此问题。
Makefile 中 target 可以对另一个 target 进行依赖(即先执行另一个 target,执行完毕后在执行当前的 target),just 也可以,而且 just 还有“后依赖”,例如:
1 | list_file := "formulae-list.json" |
在执行了 just generate-list
之后,会执行 preview
这个 recipe。
1 | recipe P='D': |
上面的例子中 recipe
有一个参数 P
,并且有个默认值 D,可以在下面的命令中用 {{P}}
来使用这个参数(使用的是mustache来进行变量替换),除此之外还可以用 *P
代表该参数有 0 到多个值,+P
表示 1 到多个值。调用的时候可以用 just recipe E
就可以改变 P
的值为 E
。
just 可以通过 set shell := ["any-available-shell", "-c"]
的方式来改变默认的 shell;另外还可以加载 .env 文件的环境变量,只需要在 justfile 文件中加入 set dotenv-load
即可。
just 默认的情况下每一行是一条命令(想要写成多行需要在每行的行尾加上 \
,使用的是默认的 shell 来执行),但是你可以通过 Shebang 来执行不同的脚本,这要看你想使用什么脚本,sh、bash、zsh、ksh、fish、python、ruby……一切支持 Shebang 的脚本,例如:
1 | push-changes: |
再如可以使用 Python 脚本写:
1 | # 数一下当前 iOS 使用了多少个 pods |
just 默认是会打印每条命令的,可以在每条命令前加上 @
来取消打印(这一点和 make 一样),如果想要整个 recipe 不打印任何执行的命令,只需要在这个 recipe 前面加上 @
即可。但是有个问题,不知道是不是 bug,就是如果你在有 Shebang 的 recipe 前加上 @
的话反而会把整个 recipe 打印出来。
just 支持私有的 recipe,可以在 recipe 前加上一个 _
或者在 recipe 的上方加上 [private]
属性让其变成私有;如果一个 recipe 设置为私有,则在执行 just -l
时就不会被列出来。
just -l
有些类似于大部分命令的 --help
参数,它会列出来所有非私有的 recipe,后面跟着每个 recipe 的注释;另外还可以通过 just --choose
交互的执行相应的 recipe(会结合 fzf
)
just 中默认会选择 justfile 文件中的第一个 recipe 来作为默认的 recipe,也可以通过 default
recipe 来指定,例如:
1 | # 列出所有可用的 recipe |
执行默认 recipe 不需要额外的参数,只需要执行 just
即可。
variable_name := variable
的形式来设置变量,可以在执行 recipe 的时候传入不同的变量,例如:1 | name := "world" |
这里定义了一个变量为 name
,默认值为字符串 world
1 | just hello # 会打印出 Hello world |
一些常见的 recipe 属性:
[private]
:将 recipe 标记为私有[no-exit-message]
:如果配方执行失败,不要打印错误信息[no-cd]
:在执行配方之前不要改变目录。这里需要注意,默认情况下 just 在执行 recipe 的时候会将当前目录改变到 justfile 所在的目录,如果加上了此属性则不会改变目录,命令执行时当前的目录(pwd
)为执行 just 时所在的目录。[linux]
,[macos]
等,详情请见 just 的文档。just 在日常还是很好用的(结合 fzf
、自己喜欢的脚本等可以实现出很有意思的功能),但是终究无法代替 make,毕竟两者的定位不同。just 重在命令的管理和组织,而 make 在是定位为编译工具,在 C/C++ 的编译方面无法撼动。这篇文章一个月前就想写了,想来想去也不知道如何着手,后来想想其实也算只是推荐而已,更详细的看说明 - Just 用户指南足够了。我在平时的工作和生活中已经大量运用 just 来帮助我做一些事情了。除了一些官方和一些开源的 justfile 示例外,也可以看一下我自己写的一个小玩意儿里的 Justfile。工作中用到的一些 recipe 就不方便分享出来了,但是如果有什么问题也欢迎留言交流。
其实做一个方便管理和切换 iOS 主题的库的想法是好几年前的事了,在这期间脑补过很多实现方案,当想开始做的时候又由于想到了方案的不足再加上我对切换主题没有什么需求,导致一直搁浅。前段时间突发奇想,来了灵感,先在脑袋里打了个草稿,几周后着手开始做,加上一两天空闲时间做的实验,最终花了我一晚上的时间(下班后,包括代码 + Demo + README + 适配各种依赖管理工具(Swift Package, Carthage, CocoaPods,包括本地测试) + 传到 CocoaPods 上,最终两点多完成)终于把这个库做出来了。所以做这玩意纯粹是个人兴趣加上突发奇想。
依照常规的想法以及大部分现有开源库的思路,基本上都是先实现一个自定义的主题类,比如:MyTheme
,在其中实现 UI 配置相关的属性,比如背景色、前景色、字体、背景图片等。然后实现一套自己的配置 UI 方面的方法,比如:my_backgroundColor
,然后有一个叫 ThemeManager
的东西通过相应的方法负责切换主题,切换主题一般是 ThemeManager
发个通知,然后 UI 控件监听了此通知,然后做出相应的修改。我在 GitHub 上看到一个别人写的基于 Swift 的管理主题的库,也不知为何这么多 star,大致看了一下代码,虽然实现了切换主题的功能,但是设置 UI 属性时是将所有主题的相应属性值赋给 UI 属性,比如 label.theme_textColor = ["#000", "#FFF"]
,切换主题时通过 ThemeManager.setTheme(index: isNight ? 1 : 0)
这种方式,这种实现方式怎么说呢。。。我觉得只能用“呵呵”来代表我的想法吧。
优点我就不说了,可能也就是能切换主题。但是缺点却是一大堆的,我可以罗列一些:
Method Swizzling
特性来实现,可能会带来一些未知的问题;我最初的思路是有个 ThemeManager
用来管理和切换主题(这个肯定都一样),然后初始注册一个主题到 ThemeManager
,但最主要的是怎么实现主题的切换。最一开始我想到的是扩展 UI 控件的方法,比如 -[UIView theme_setBackgroundColor:]
, 内部通过 -[ThemeManger setViewObject:forUIKeyPath:toValueOfThemeKeyPath:]
的方法将这些数据记录到 NSMapTable
数据类型的属性中,切换主题时只需要遍历这个属性,执行相应的方法即可实现主题的切换。
初看上去很不错哦,特别是 Swift 4 之后加入了 KeyPath
特性,都是后面一想还是不行的。因为有些是通过方法来设置 UI 属性的,比如说:-[UIButton setTitleColor:forState:]
,这种通过这样的方法就不好实现(当然也能实现,只是感觉比较丑陋、不够优雅,不是我想要的结果)。
就算以上的想法都实现了,但是还有一个问题我无法接受,就是内存占用问题。虽然用了 NSMapTable
,但是 UIView
释放掉之后只是其 Key 自动变成了 nil
,但是保存的 Value 仍然在内存中没有释放。何时以及怎么释放这些 Value 是个问题。
就是因为这些问题,困扰了我好几年的时间。前段时间突然开窍了,想到一个绝妙的方法来实现 UI 属性设置的问题,那就是通过闭包将设置属性的设置放进去,然后通过 ThemeItem
的结构体将 UI 和设置属性的闭包进行关联和存储。
这个是实现了,但是怎么做到 UI 释放掉之后自动清空其闭包呢?这个也是费了我几天的时间来思考,开始的想法是通过 Method Swizzling
来交换 dealloc
方法,然后在交换方法中移除相应的 ThemeItem
。但是代码写完了之后发现编译器报错了,是不允许交换 dealloc
方法的,网上搜了一下发现可以通过 NSSelectorFromString
的函数进行交换,试了一下结果并没有任何卵用,交换是失败的,没有走。。。最后突然来了灵光闪现,我可以通过 objc runtime
的关联对象特性来解决这个问题呀!
我创建了一个 DeallocObserver
的类,里面放了两个属性,一个是记录需要观察被释放的对象的内存地址,这样我不需要对被观察对象进行引用,不会影响被观察者的生命周期;另一个是被观察对象被释放后需要执行的闭包,闭包有个参数值,就是存储的已释放对象之前的内存地址。我将 DeallocObserver
关联到被观察对象上,当被观察对象释放的时候,由于已经没人在引用 DeallocObserver
了,所以 DeallocObserver
也会紧接着被释放,此时就会执行到 DeallocObserver
的闭包,告诉外面做一些清理工作。比如我用来存储 ThemeItem
的变量 private var managedItems = [Int: [ThemeItem]]()
,因为 managedItems
是个字典,Key 值为被观察对象的内存地址,所以只需要执行 managedItems.removeValue(forKey:#memory address#)
即可。
Perfect! 所有的问题都解决了,实现方式超乎寻常的简单,最终代码只有不到 90 行,具体实现可以看一下 ThemeManager.swift。
由于第一版仅支持基于 UIView 的对象,发出去之后没几天想到一个想到一个了一个问题,有一些非继承自 UIView 的类就无法实现主题的切换了,比如一些继承自 UIBarItem
的类。那何必将其限定死为 UIView
呢?于是我将 ThemeItem
的类型限定开放为 AnyObject 了。
这样做有什么好处?好处显而易见,我不经过可以动态设置 UIView
的属性,我还可以动态设置其它类型的属性,比如:UIBarButtonItem
的 title
、image
等。另外我还改造了 ThemeManager
的 setup
方法,参数变为 Optional 参数,如果为 nil
就什么都不做,极大的方便了编码效率,举个例子,比如需要设置 navigationController?.navigationBar
的属性,我只需要通过如下代码即可轻松实现:
1 | themeManager.setup(navigationController?.navigationBar) { (bar, theme) in |
除了 UI 的样式之外,后来我也想到,我的 ThemeManager
不仅仅可以做主题的切换,还可以做多语言的切换等所有你能想得到的它可以实现的东西,而所有这些没有动到 UIKit
的任何一点东西,比如想做一个多语言的切换,可以简单的实现如下方法:
1 | // 需要实现 Theme 协议 |
这些功能虽然足够用了,但是我觉得还可以做得更多。~~iOS 13 马上就要发布了,大家都知道 iOS 13 加入了黑色主题的支持,下一步我想做的就是监听系统黑白主题的的变化,通过回调告诉外面,然后你自己做决定是否根据系统主题的切换而切换当前主题。~~虽然系统提供了自动切换题的方法,但是系统将你的主题限定死了,只能使用两种主题,想要实现更多的主题,通过我这个库来实现,没错的~。除了这个之外,我还想在下一版支持自定义动画切换功能。
1 | // register notification |
Theme
如何实现,不管你是怎么加载和保存主题的,你要做的只是实现我的 Theme
这个空的协议;ThemeManager
,它一样可以运行,并实现你想要的所有新特性;最后,如果你觉得这个库还可以的话,那么欢迎您使用并 star,欢迎提出宝贵意见或者 PR。GitHub 地址:https://github.com/azone/ThemeManager。由于英文不够好,README 又全是用英文写的(真是胆大,献丑了😆),如果有什么不对的地方还望批评指正!
刚才发现搜狐的畅言竟然给我加了一些恶心的广告,果断弃用,还是改用 disqus 吧,disqus 也是支持广告的,是可配置的,我准备开启一下试试。
]]>Swift
了。开始是没发现什么问题的,都是但是到后来发现截图总是其中一个的(看那个先出来,基本上不是想要的那个),而不是想要的那个。仔细研究后发现,OpenGL 的截图是根据 RenderBuffer 来截取的,由于没有指定 RenderBuffer 所以截取的图只是其中某一个。知道了这个也没用,由于这个 View 用的是第三方库,所以不知道这个 View 的 RenderBuffer。。。后果经过 Debug,发现这个 View 有个 _colorRenderBuffer
属性,这就好办啦~。经过改造后代码如下:
1 | func snapshotForEAGLView(glView: UIView) -> UIImage { |
很棒!但是很快又发现了问题,挂时间稍长就会崩溃(这个在之前也是有的,只是精力全放在另外一个问题上了)!经过调试后发现有很严重的内存泄露问题!!!从代码可以很容易看出是由于 imageBytes
没有释放的原因,于是在 let dataProvider = CGDataProviderCreateWithData(nil, imageBytes, Int(dataLength), nil)
下面加了两行代码来释放内存:
1 | imageBytes.destroy() |
这样做虽然内存释放掉了,但是又出问题了。。。截的图不见了,调试发现图是空的。经过分析得知由于 UIImage
、dataProvider
和 imageRef
用的是一块内存,所以上面两行代码等于把 UIImage 的内存也释放掉了。
现在要做的就是把 imageBytes
拷一份出来,这样再释放 imageBytes
就不会有影响了。通过查文档得知 CGDataProviderRef
可以通过 CGDataProviderCreateWithCFData
来创建,CFDataRef
又可以通过 CFDataCreate
来创建,通过 CFDataCreate
的官方文档得知是通过拷贝指定的缓冲字节来创建 CFData
的:
Creates an immutable CFData object using data copied from a specified byte buffer.
虽然饶了一点,但是也算是找到好方法了,只要把 let dataProvider = CGDataProviderCreateWithData(nil, imageBytes, Int(dataLength), nil)
改成下面的代码就解决啦!
1 | let data = CFDataCreate(nil, imageBytes, CFIndex(dataLength)) |
最终代码:
1 | func snapshotForEAGLView(glView: UIView) -> UIImage { |
categories/xcode
,现在是 categories/Xcode
)今天就来说一下我是怎么解决这三个问题:
需要自定义一个 404 页面(在 source
目录下创建一个 404 页面_参考_)。本来想做一下404公益的,但是由于腾讯公益404是完全替换了我的404页面的内容,无法达到我的效果,所以舍弃了;404公益_益云(公益互联网)社会创新中心倒是可以,但是在没有适应移动设备(宽高限定死了),所以也舍弃了;失蹤兒童少年資料管理中心404是台湾的网站,没有试,所以也舍弃了。
创建好页面之后在文件中写入如下内容:
1 | --- |
之后再重新生成和发布就可以了。
上述代码的作用就是现实一个无序列表,里面有一条“返回主页”链接,通过 js 判断当前网址的路径是否以 /blog/
开头,如果是则创建一个链接,把 /blog/
去掉并生成新的网址插入到无序列表中。
首先需要改一下配置文件
1 | # 将此选项设置成 1, 设置成 1 的意思是全部转成小写 |
如果之前有部署再传上去是会有问题的,虽然之前的大写字母现在已经全部换成了小写,但是传不到 Github 上,可能是 git 忽略了大小写吧。我的方法是直接把原来的仓库删掉重建一遍,然后再上传,反正也无所谓(本地记得删除 .deploy_git
目录和执行 hexo clean
命令)。
这个问题就更简单了,直接找到原来的网站备份,把遗失的文件和目录上传到相应的地方(source
目录下)就行了。
建议通过 Homebrew 安装,原因是安装、升级和删除简单方便。当然也可以通过官网的安装包,但是通过安装包安装会有个问题,由于 /usr/local/lib/dtrees
和 /usr/local/lib/node_modules
目录的属主是 root:wheel 而不是当前用户,所以在执行 npm install hexo-cli -g
的时候会无法写入 /usr/local/lib/node_modules
而导致安装失败。如果用了 sudo
会有本地没有安装 hexo
的错误,让执行 npm install hexo --save
命令,因此也无法使用。
通过上述方法安装完 Node.js 之后执行 npm install -g hexo-cil
即可完成安装。
hexo init [blog directory name]
初始化博客目录,如果后面目录名没有指定则是当前目录hexo g[enerate]
生成静态文件hexo config
获取和配置你的博客,可以通过 _config.yml
来直接更改配置(推荐!因为比较直观,但是要对 Yaml 格式有一定的了解,当然也是非常简单的)hexo n[ew] <[post]|page> <title>
创建文章或页面hexo s[erver]
建立并开启本地服务器,默认网址是 http://0.0.0.0:4000hexo d[eploy]
部署博客hexo m[igrate]
从其他平台迁移到 Hexohexo --help
显示帮助,可以查看别的命令的帮助Hexo 的配置也是很简单的,只需要简单配置如下选项即可(其他的根据需要自己选择性的改一改就行了):
1 | # Site,网站基本信息相关的内容 |
预览之后才发现 Hexo 不支持对 Yaml 格式代码的高亮。
由于我之前用的是 Octopress 而且他们的格式是相互兼容的,所以最简单的办法是只需要把之前 Octopress 下 _post
目录拷贝到 Hexo 即可。但是有一点需要注意,一定要将之前的 .markdown
后缀改成 .md
,否则文章生成的地址会是 /yyyy/MM/dd/yyyy-MM-dd-title
而不是 /yyyy/MM/dd/title
如果是通过 git 或者 Github 部署的,则需要通过npm install hexo-deployer-git --save
先安装 hexo-deployer-git 插件。
当然也可以安装 Hexo 提供的 插件 ,然后通过 hexo m[igrate] <type>
迁移,最后通过 hexo d[eploy]
来部署。
由于今天才开始部署 Hexo,所以对 Hexo 的插件了解的也不是太多,只装了几个:
该说的上面已经说了,总的来说还是很棒的。最后决定把评论系统从 Disqus 迁移到多说,毕竟多说是国内的,速度应该会快很多,Disqus 说不定哪天就被墙了。由于评论内容前一起来非常麻烦,而且评论内容非常少,所以决定评论内容就不迁移了。不说了,走起~
注意:文章中方括号中括起来的部分为可选部分
]]>首先以我为主程的一个应用(绝大部分代码是我写的)由于之前数据库设计的不合理导致应用总是无规律可循的崩溃。这个问题一直存在了好几个月,怎么研究都是无果。后来直到看到Stack Overflow上Core Data: EXC_BAD_ACCESS accessing relationship的帖子(后来还看到这个Semantic Issue: Property’s synthesized getter follows Cocoa naming convention for returning ‘owned’ objects)才意识到自己的愚蠢和无知!我忘记了Objective-C的属性名(Core Data中包括属性名和关系名)不能以new打头,否则就违反了Objective-C的内存管理策略。
请记住:尽量不要使用以alloc、new、copy和mutableCopy等打头的属性名、关系名和方法名,如果必须使用请在声明中使用NS_RETURNS_RETAINED
或NS_RETURNS_NOT_RETURNED
,否则会导致内存管理问题!!!
既然知道了错了那就改吧!于是就对Core Data的字段进行修改。之前只是大致了解过Core Data数据库迁移的基本知识,之前也做过一些(只不过之前都是轻量级迁移)。直到这次改关系名是个重量级的迁移,所以就以我自己的理解做了个mapping model。结果把Relationship Mappings
中的Mapping Name
给搞错了,搞成它自己了~。最终导致应用上架后升级用户大面积崩溃,后果很严重,老板和领导很生气。后来应用直接下架了,据说公司损失严重,还说开发的能力不行,给我制造了不少压力。
既然有知道原因了那就及时改正吧~,更正了Relationship Mappings
中的Mapping Name
,然后经过粗略的测试紧急上架。这次走了苹果的加急通道,上架速度果然很快~,但是第二天又有很多人反馈崩溃问题,看了Crashlytics的后台,发现全是iOS 6及以下的设备。最终研究后发现当用户本地数据库数据量比较大的时候老版本升上来很多都会崩溃(性能好一点机器的几率比较小),后来找到原因了,我又犯了一个低级的错误!当时没考虑到很多人数据量比较大,没不清楚Core Data进行重量级迁移的时候需要耗费很久的时间。我把Core Data的自动迁移放到了应用的入口函数,阻碍了入口函数的返回,导致应用启动时间过长直接被系统咔嚓掉了,也有一部分用户由于迁移失败导致数据库数据残缺导致的崩溃。
以前买过一本Marcus S. Zarra的书Core Data (2nd edition): Data Storage and Management for iOS, OS X, and iCloud,只是看了部分内容而且Core Data数据迁移这一块没怎么看。这次翻来自己看了一下,对一些概念搞清楚了,后来有看到objc.io上的Custom Core Data Migrations(里面也引用到了一部分Core Data (2nd edition): Data Storage and Management for iOS, OS X, and iCloud中的代码)和 BookMigration实例。都讲到了Progressive Migrations,搞的我也想尝试一下手动的数据迁移。于是有搜索了Google、StackOverflow和苹果文档等,最后发现mapping model是可以拆分的,也就是说一次升级可以拆分成多个mapping model(每个mapping model不能和另外的mapping model有任何关系,逻辑上要是独立的),这样可以减少内存开支。在BookMigration中又发现NSMigrationManager有一个migrationProgress属性,用来获取当前迁移进度,感觉真是太棒了,不用一直在那里转菊花了。。。
经过我自己的深入研究(实践)发现+[NSMappingModel mappingModelFromBundles:forSourceModel:destinationModel:]
不是真正的靠source model和destination model来匹配的,而是通过mapping model与source model还有destination model的entity version hashes来进行对比的,而且它也只返回一个mapping model,如果拆分成多个mapping model的话就无法找到其他的了。而+[NSManagedObjectModel mergedModelFromBundles:forStoreMetadata:]
也不是找出下一个版本的model,而是如果发现有+[NSMappingModel mappingModelFromBundles:forSourceModel:destinationModel:]
和当前(source)model匹配的话就会返回那个model。
后来我想到了一个绝佳的迁移Core Data数据库的方法:如果是轻量级迁移则用轻量级迁移,否则进行重量级迁移的逐步迁移方法,这样就可以用极少的mapping model做大跨度的版本迁移了。摸清了这些就可以对上述的实例进行一些改造了
-[NSMappingModel initWithContentsOfURL:]
生成NSMappingModel对象,将source model与destination model的entity version hashes与mapping model的sourceEntityVersionHash
和destinationEntityVersionHash
进行对比。如果mapping model等于或是source model和destination的子集则说明这个mapping model是升级到下一个版本的mapping modelsourceEntityVersionHash
与destinationEntityVersionHash
交集等于source model与destination model的entity version hashes则说明这是个重量级的迁移,否则说明出轻量级迁移。具体方法请参见:WYZCoreDataMigrationManager,觉得还是有一些地方需要改进的,只能等后面有机会再改进了。
另外还需要一些值得注意的地方,比如如果之前的Core Data的journal_mode是WAL,迁移的时候也要吧shm文件和wal文件一块迁移,否则可能会导致数据会乱;有时候需要自己定义Migration Policy等,我在这里就不多说了,实际情况还是需有具体问题具体对待。
]]>GIF动画大家应该都知道,但是在iOS中却不能直接播放它,只能显示它的第一帧画面。iOS中可以使用UIImageView并通过其animationImages、animationDuration和animationRepeatCount等属性来播放连续图片的动画。UIImage在iOS 5.0之后增加了+ animatedImageNamed:duration:和+ UIImage animatedImageWithImages:duration:等方法,通过这些方法创建的UIImage对象赋值给UIImageView的image属性,同样也可以播放动画。
我们如果想播放GIF动画,其实要做的就是将GIF动画中的每一帧拿出来转化成UIImage,放到一个数组,然后在通过读取GIF每一帧的延时时间并累加得出一个总时长,做完以上两步之后在通过上面的方法就可以播放GIF动画了。但是通过这种方式创建的动画有一个缺点,因为GIF动画很有可能不是匀速动画,它的每一帧延时可能都会不同。于是WYZGIFAnimationView诞生了,说到这里突然想到WYZGIFAnimationView还没有支持这一点,我要赶紧修正一下啦~
]]>记得之前了解到iOS 7的UIResponder新增了keyCommands方法,于是今天就研究了一下API文档并且测试了一下,感觉真是太棒了~
下面介绍一下如何让应用支持外接键盘的快捷键,其实很简单!
首先要确保在需要实现键盘快捷键的ViewController中加入如下代码:
1 | - (BOOL)canBecomeFirstResponder { |
然后就是重写UIViewController
的- (NSArray *)keyCommands
方法:
1 | - (NSArray *)keyCommands { |
最后一步就是实现UIKeyCommand
的action
:
1 | - (void)escapeKeyPressed:(UIKeyCommand *)keyCommand { |
现在试一下运行app,然后按一下esc
键(模拟器下可以使用电脑的键盘),看一下效果~,怎么样?很棒吧!!!
参考文档:
]]>{{ root_url }}
却怎么也显示不出来~,后来在Octpress的Issues页面问了一下才知道如何才能不把那些用来显示的代码块中的变量不被解析,具体看https://github.com/imathis/octopress/issues/941#issuecomment-12206038或者Stack Overflow的How to escape liquid template tags?]]>⌘+0
显示/隐藏导航栏⌥+⌘+0
显示/隐藏工具栏⇧+⌘+O
快速打开⌘+单击
在主编辑器中显示定义⌥+⌘+单击
在助手编辑器中显示定义⌥+⌘+⏎
显示助手编辑器⌘+⏎
隐藏助手编辑器⌘+E
再按 ⌘+G
搜索当前所选文本,继续按⌘+G
搜索下一个⌃+⌥+⌘+2
打开代码片段库⌘+J
多个窗口之间切换⌥+→/←
先前/向后移动一个单词⌃+b/f/n/p
向前/向后移动一个字符,向上/向下移动一行⌃+⌘+E
当前编辑器全局编辑当前光标所在的变量名或方法名⌃+⇧+⌘+/
快速帮助⌘+双击
在新窗口中显示定义⌃+⌥+⌘+J
在助手编辑器中显示定义{
选择代码块⌥
会将搜索栏的 “Replace All” 变成 “In Selection”TODO: xxx
或者FIXME: xxx
即可他们在视频中介绍了一个Automator服务,通过这个服务来达到对引入的头文件进行排序和过滤重复的功能,方法很简单,具体设置见图:
另外在这个视频中他们还介绍了Behavior、Tab和新窗口等的使用技巧来提高效率,很值得一看。
]]>代码如下:
用法很简单:
1 |
|
之前我一直没有用过系统自带的Automator这个强大的工具,更是没有想到要用,后来看了@ibuick的《OS X Mountain Lion高手进阶》之后做一些操作之前总是先想一下能不能用Automator来代替我自动批量完成。下面了介绍一下怎么来通过Automator来做一个批量重命名的工具吧。
最后让大家看一下设置截图:
以后如果想给文件批量重命名的时候只需要选择需要重命名的文件,然后选择“” -> “服务”或者点击右键选择服务,找到“批量重命名”就可以了~
最后附上我制作的batch-rename.workflow.zip,你可以下载之后解压,然后双击安装。
]]>通过GitHub+Octopress搭建的博客有几个很显而易见的有点:
废话不多说了,下面就介绍来一下博客的搭建过程。
这个很简单,只需要到GitHub按照上面的提示注册就可以了
装好之后需要对git进行简单的配置(如果是Windows需要打开Git Shell)
1 | git config --global user.email "your-email@example.com" |
然后生成证书(如果是Windows需要打开Git Shell)
1 | ssh-keygen -t rsa -C "your-email@example.com or other message" |
然后拷贝~/.ssh/id_rsa.pub(Windows一般在C:\Users<username>.ssh\id_rsa.pub)的内容,登陆GitHub,进入"Account Settings",点击"SSH Keys",再点"Add SSH Keys",将id_rsa.pub的内容粘贴到"Key"中,并输入"Title"
使用Octopress需要Ruby版本为1.9.3,如果小于这个版本则无法使用。下面介绍一下如何安装Ruby~
如果你用的操作系统是非Windows则比较简单,强烈建议安装rbenv或者RVM,通过它们来安装ruby1.9.3,这样不会对你系统本身造成任何影响。以个人经验在Mac下安装Ruby比较麻烦一些。如果你之前安装过XCode4.1(必须装了Command Line Tools)或者之前的版本是比较简单的,否则需要先安装apple-gcc42
,apple-gcc42
你可以通过Homebrew(源代码)或者OSX GCC Installer(源代码)来安装。
如果你用的是Windows的话需要通过RubyInstaller来安装安装Ruby,到上面的页面下载最新的Ruby和DevKit。首先安装Ruby(记得把Ruby加入到系统PATH变量,安装的时候有这个选项),然后双击解压下载的DevKit(自解压文件),通过命令提示符cd到解压的目录,执行ruby dk.rb init
,然后再执行ruby dk.rb install
,这样ruby就安装好了。
配置Octopress其实还是蛮简单的,只要根据官网上的文档一步一步来就可以了。我就简要介绍一下步骤吧(Windows下要在Git Shell命令行中执行命令)~
首先要到GitHub上创建一个仓库,仓库的名字是<username>.github.com
,其中<username>
是你的github上的用户名。创建好之后需要进入刚才建立的仓库,然后进入"Settings",往下滚你会看到"GitHub Pages",里面有个"Automatic Page Generator"按钮,点击它。然后是生成的首页和内容,别管它只需要点下面的"Continue to Layouts"按钮就行。接着就是选择模板,随便选一个然后按"PUBLISH"按钮即可。大约十分钟之后就可以看到通过"Automatic Page Generator"生成的页面了,当然这个你不需要管它,过一会会被Octopress生成的静态页面给替换掉。
接下来就要把Octopress克隆下来了,在终端输入如下命令(可以参考http://octopress.org/docs/setup/):
1 | cd /path/where/you/want/to/clone |
然后是进行部署配置,执行命令(参考网址http://octopress.org/docs/deploying/github/):
1 | rake setup_github_pages |
它会询问你并让你输入刚才所创建的仓库的地址:git@github.com:<username>/<username>.github.com
,接下来是要配置一下你的_config.yml文件(参考网址:http://octopress.org/docs/configuring/),其实也比较简单,根据自己的实际情况修改一下就行了,这里就不做过多的解释了。
然后你可执行一下命令来预览你的Blog:
1 | rake generate # 生成文件 |
如果你觉得我没有问题就可以通过下面的命令将博客部署到GitHub上了
1 | rake deploy # 会将通过rake generate生成的文件_deploy下的内容push到GitHub的master分支上 |
记得输入如下命令将所有的改动push到GitHub上
1 | git add . |
在终端中执行如下命令:
1 | rake new_post['Post Title'] |
此命令会在source/_post/目录下生成一个类似于2012-01-05-post-title.markdown的文件,用Markdown编辑器编辑这个文件,最上面的---
中间的部分是yml格式的一些关于文章的信息,具体含义请参考:https://github.com/mojombo/jekyll/wiki/yaml-front-matter
编辑好文章之后执行如下命令:
1 | rake generate # 生成文件 |
然后你应该就可以在http://<username>.github.com
上看到你写的文章了,更多命令请参考http://octopress.org/docs/blogging/
如果你想绑定自己的域名也是可以的,方法也很简单~。首先要参考GitHub上的帮助文章https://help.github.com/articles/setting-up-a-custom-domain-with-pages来设置自己域名的DNS。然后执行下面的命令(参考):
1 | echo 'yourdomain.com' > source/CNAME |
接下来就是等待域名解析生效了~
记得每次写完文章之后用如下命令将写的文章同步到GitHub上:
1 | git add -A |
这样你的文章就不会丢失了,哪怕是重装电脑,下次只需要把GitHub仓库clone下来然后重做上面的某些步骤就可以了!不过你也可以将文件保存到Dropbox上,也是很方便的。
]]>