Long Blog Post
This is the summary of a very long blog post,
Use a <!--
truncate
-->
comment to limit blog post size in the list view.
This is the summary of a very long blog post,
Use a <!--
truncate
-->
comment to limit blog post size in the list view.
Update: 我最后还是放弃把 Vim 作为主要编辑器来输入中文了,整体使用下来 mental model 的 cost 太重了。记笔记时用用中文呀或者改改博客时偶尔用一下还蛮去,这个时候这个功能至少能帮助你 Esc 之后不煞笔,所以也不算完全没有价值吧……
我相信很多中文世界的 Vimer 都遇到过这个烦恼,在 vim 的 insert 模式时可能突然想输个中文,输完之后会本能的直接 esc
接 normal 模式操作,结果发现跳出来的是中文输入法……对于 vscode,我一般会在几次错误之后被逼到退出 vscode vim 模式,而对于终端中用的 neovim,就只能尽量不输入中文了。
为了满足我 1% 用 vim 输入中文的场景(比如写博客),我还是想看看有没有什么解决方案,Google 出来的解决方案基本是:在退出 insert 模式时记住当时的输入法,并自动切换到默认输入法(一般是英文)给 normal 模式用,并且在下一次进入 insert 模式时再切换回来。
原生 vim 的话,可以使用 smartim 插件,原理是调用 im-select 这个 CLI 工具来切换输入法。
对于 VSCode-vim 的话,smartim 的移植也在近期的 PR 中被 merge 到了插件里,详情见文档的这部分配置,需要指定一下默认输入法和 im-select 的 binary 路径就好。
不过实话说,在 vim 中编辑中文的效率和体验和英文比都是大打折扣的。因为中文分词难度太高,不像英文可以简单依靠一个 split " "
搞定。所以其实无论 vim(w
ord,b
egin,e
nd),emacs 还是操作系统自带的(比如 macOS 中的 alt + 箭头
) 「按词移动」功能对于中文都仅仅是跳转到下一个空格处而已,对于中文来说基本就是下一句了……其他常用操作诸如 f
,/
, r
eplace, t
ill 也都无法很好的工作,基本只能靠 hjkl
爬行……
不过也算聊胜于无吧,由于我的主力外置键盘是 HHKB,能用 vim 操作的一个子集(hjkl
, o
, A
, I
, v
etc.)可能也比按住 Fn
的方向键好用……
这篇博客九月就想写了,因为赶项目拖了到现在,抓住17年尾巴写吧~
上次看了一篇 《从一道网易面试题浅谈OC线程安全》 的博客,主要内容是:
作者去网易面试,面试官出了一道面试题:下面代码会发生什么问题?
@property (nonatomic, strong) NSString *target;
//....
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}
答案是:会 crash。
我们来看看对target
属性(strong
修饰)进行赋值,相当与 MRC 中的
- (void)setTarget:(NSString *)target {
if (target == _target) return;
id pre = _target;
[target retain];//1.先保留新值
_target = target;//2.再进行赋值
[pre release];//3.释放旧值
}
因为在 并行队列 DISPATCH_QUEUE_CONCURRENT
中异步 dispatch_async
对 target
属性进行赋值,就会导致 target 已经被 release
了,还会执行 release
。这就是向已释放内存对象发送消息而发生 crash 。
我敲了这段代码,执行的时候发现并不会 crash~
@property (nonatomic, strong) NSString *target;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
// ‘ksddkjalkjd’删除了
self.target = [NSString stringWithFormat:@"%d",i];
});
}
原因就出在对 self.target
赋值的字符串上。博客的最后也提到了 - ‘上述代码的字符串改短一些,就不会崩溃’,还有 Tagged Pointer
这个东西。
我们将上面的代码修改下:
NSString *str = [NSString stringWithFormat:@"%d", i];
NSLog(@"%d, %s, %p", i, object_getClassName(str), str);
self.target = str;
输出:
0, NSTaggedPointerString, 0x3015
发现这个字符串类型是 NSTaggedPointerString
,那我们来看看 Tagged Pointer 是什么?
Tagged Pointer 详细的内容可以看这里 深入理解Tagged Pointer。
Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。
由于 Disqus 对于国内网路的支持十分糟糕,很多人反映 Disqus 评论插件一直加载不出来。而我一直是处于翻墙状态的~(话说你们做程序员的都不翻墙用Google的吗😅,哈哈,吐嘈下)
针对这个问题,我添加了Gitalk 评论插件。在此,非常感谢 @FeDemo 的推荐 。
首先来看看 Gitalk 的界面和功能:
gitalk 使用 Github 帐号登录,界面干净整洁,最喜欢的一点是支持 MarkDown语法
。
Gitalk 是一个利用 Github API,基于 Github issue 和 Preact 开发的评论插件,在 Gitalk 之前还有一个 gitment 插件也是基于这个原理开发的,不过 gitment 已经很久没人维护了。
可以看到在 gitalk 的评论框进行评论时,其实就是在对应的 issue 上提问题。
到这里,你应该对 Gitalk 有个大致的了解了,现在,开始集成 gitalk 插件吧。
将这段代码插入到你的网站:
<!-- Gitalk 评论 start -->
{% if site.gitalk.enable %}
<!-- Link Gitalk 的支持文件 -->
<link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
<script src="https://unpkg.com/gitalk@latest/dist/gitalk.min.js"></script>
<div id="gitalk-container"></div>
<script type="text/javascript">
var gitalk = new Gitalk({
// gitalk的主要参数
clientID: `Github Application clientID`,
clientSecret: `Github Application clientSecret`,
repo: `存储你评论 issue 的 Github 仓库名`,
owner: 'Github 用户名',
admin: ['Github 用户名'],
id: '页面的唯一标识,gitalk会根据这个标识自动创建的issue的标签',
});
gitalk.render('gitalk-container');
</script>
{% endif %}
<!-- Gitalk end -->
我们需要关心的就是配置下面几个参数:
clientID: `Github Application clientID`,
clientSecret: `Github Application clientSecret`,
repo: `Github 仓库名`,//存储你评论 issue 的 Github 仓库名(建议直接用 GitHub Page 的仓库名)
owner: 'Github 用户名',
admin: ['Github 用户名'], //这个仓库的管理员,可以有多个,用数组表示,一般写自己,
id: 'window.location.pathname', //页面的唯一标识,gitalk 会根据这个标识自动创建的issue的标签,我们使用页面的相对路径作为标识
当然,还有其他很多参数,有兴趣的话可以 点这里。
比如我就增加了这个全屏遮罩的参数。
distractionFreeMode: true,
Gitalk 需要一个 Github Application,点击这里申请。
填写下面参数:
点击创建
获取 Client ID
和 Client Secret
填入你的我们 Gitalk 参数中
当你参数都设置好,将代码推送到 Github 仓库后,没什么问题的话,当你点击进入你的博客页面后就会出现评论框了。
当你用 github 帐号登录(管理员),并且第一次加载该会比较慢,因为第一次加载会自动在你 repo
的仓库下创建对应 issue。
比如说这样:
当然,你也可以手动创建issue作为 gitalk评论容器。只要有 Gitalk
标签 和 id
对应标签就可以。比我我自己创建的 About issue 。
最后说几句吐嘈几句, Gitalk 需要你点开每篇文章的页面才会创建对应的 issue,对我来说真是个糟糕的体验(文章有点多~)。
当然,也有解决办法,这篇 自动初始化 Gitalk 和 Gitment 评论,就解决了这个问题。
最后,给个 star 吧~
这篇文章转载自我在知乎上的回答
我用 Python 伪代码来解释下,我觉得对这个问题有兴趣的应该都是有点编程基础的,所以直接上 code 应该是最容易的。
「停机问题」研究的是:是否存在一个「程序」,能够判断另外一个「程序」在特定的「输入」下,是会给出结果(停机),还是会无限执行下去(不停机)。
在下文中,我们用「函数」来表示「程序」,「函数返回」即表示给出了结果。
我们假设存在这么一个「停机程序」,不管它是怎么实现的,但是它能够回答「停机问题」:它接受一个「程序」和一个「输入」,然后判断这个「程序」在这个「输入」下是否能给出结果:
def is_halt(program, input) -> bool:
# 返回 True 如果 program(input) 会返回
# 返回 False 如果 program(input) 不返回
(在这里,我们通过把一个函数作为另一个函数的输入来描述一个「程序」作为另一个「程序」的「输入」,如果你不熟悉「头等函数」的概念,你可以把所有文中的函数对应为一个具备该函数的对象。)
为了帮助大家理解这个「停机程序」的功能,我们举个使用它的例子:
from halt import is_halt
def loop():
while(True):
pass
# 如果输入是 0,返回,否则无限循环
def foo(input):
if input == 0:
return
else:
loop()
is_halt(foo, 0) # 返回 True
is_halt(foo, 1) # 返回 False
是不是很棒?
不过,如果这个「停机程序」真的存在,那么我就可以写出这么一个「hack 程序」:
from halt import is_halt
def loop():
while(True):
pass
def hack(program):
if is_halt(program, program):
loop()
else:
return
这个程序说,如果你 is_halt
对 program(program)
判停了,我就无限循环;如果你判它不停,我就立刻返回。
那么,如果我们把「hack 程序」同时当做「程序」和「输入」喂给「停机程序」,会怎么样呢?
is_halt(hack, hack)
你会发现,如果「停机程序」认为 hack(hack)
会给出结果,即 is_halt(hack, hack)
) 返回 True
) ,那么实际上 hack(hack)
会进入无限循环。而如果「停机程序」认为 hack(hack)
不会给出结果,即 is_halt(hack, hack)
返回 ,那么实际上 hack(hack)
会立刻返回结果……
这里就出现了矛盾和悖论,所以我们只能认为,我们最开始的假设是错的:
这个「停机程序」是不存在的。
简单的说,「停机问题」说明了现代计算机并不是无所不能的。
上面的例子看上去是刻意使用「自我指涉」来进行反证的,但这只是为了证明方便。实际上,现实中与「停机问题」一样是现代计算机「不可解」的问题还有很多,比如所有「判断一个程序是否会在某输入下怎么样?」的算法、Hilbert 第十问题等等,wikipedia 甚至有一个 List of undecidable problems。
如果你觉得只是看懂了这个反证法没什么意思:
中文:
英文:
这篇文章转载自我在知乎上的回答
对我来说,CSS 难学以及烦人是因为它**「出乎我意料之外的复杂」且让我觉得「定位矛盾」**。
@方应杭 老师的答案我赞了:CSS 的属性互不正交,大量的依赖与耦合难以记忆。
@顾轶灵 @王成 说得也没错:CSS 的很多规则是贯彻整个体系的,而且都记在规范里了,是有规律的,你应该好好读文档而不是去瞎试。
「CSS是一门正儿八经的编程语言,请拿出你学C++或者Java的态度对待它」
但是问题就在这了,无论从我刚学习前端还是到现在,我都没有把 CSS 作为一门正儿八经的编程语言(而且显然图灵不完全的它也不是),CSS 在我眼里一直就是一个布局、定义视觉样式用的 DSL,与 HTML 一样就是一个标记语言。
写 CSS 很有趣,CSS 中像继承、类、伪类这样的设计确实非常迎合程序员的思路,各种排列组合带来了很多表达上的灵活性。但如果可以选择,在生产环境里我更愿意像 iOS/Android/Windows 开发那样,把这门 DSL 作为 IDE WYSIWYG 编辑器的编译目标就可以了,当然你可以直接编辑生成的代码,但我希望「对于同一种效果,有比较确定的 CSS 表达方式」
因为我并不在 CSS 里处理数据结构,写算法、业务逻辑啊,我就是希望我能很精确得表达我想要的视觉效果就可以了。如果我需要更复杂的灵活性和控制,你可以用真正的编程语言来给我暴露 API,而不是在 CSS 里给我更多的「表达能力」
CSS 语言本身的表达能力对于布局 DSL 来说是过剩的,所以你仅仅用 CSS 的一个很小的子集就可以在 React Native 里搞定 iOS/Android 的布局了。你会发现各个社区(典型如 React)、团队都要花很多时间去找自己项目适合的那个 CSS 子集(so called 最佳实践)。而且 CSS 的这种复杂度其实还挺严重得影响了浏览器的渲染性能,很多优化变得很难做。
而 CSS 的表达能力对于编程语言来说又严重不够,一是语言特性不够,所以社区才会青睐 Less、Sass 这些编译到 CSS 的语言,然后 CSS 自己也在加不痛不痒的 Variable。二是 API 不够,就算你把规范读了,你会发现底层 CSSOM 的 Layout、Rendering 的东西你都只能强行用声明式的方式去 hack(比如用 transform 开新的 composition layer)而没有真正的 API 可以用,所以 W3C 才会去搞 Houdini 出来。
这种不上不下的感觉就让我觉得很「矛盾」,你既没法把 CSS 当一个很简单的布局标记语言去使用,又没办法把它作为一个像样的编程语言去学习和使用。
在写 CSS 和 debug CSS 的时候我经常处在一种「MD 就这样吧反正下次还要改」和「MD 这里凭什么是这样的我要研究下」的精分状态,可是明明我写 CSS 最有成就感的时候是看到漂亮的 UI 啊。
以上。
Swift 中,对 GCD 语法进行了彻底改写。引入了 DispatchQueue
这个类。
先来看看在一个异步队列中读取数据, 然后再返回主线程更新 UI, 这种操作在新的 Swift 语法中是这样的:
DispatchQueue.global().async {
DispatchQueue.main.async {
// 更新UI操作
}
}
DispatchQueue.global().async
相当于使用全局队列进行异步操作。然后在调用 DispatchQueue.main.async
使用主线程更新相应的 UI 内容。
新的 GCD 引入了 QoS (Quality of Service) 的概念。
先看看下面的代码:
DispatchQueue.global(qos: .userInitiated).async {
}
QoS 对应的就是 Global Queue
中的优先级。 其优先级由最低的 background
到最高的 userInteractive
共五个,还有一个未定义的 unspecified
。
public static let background: DispatchQoS
public static let utility: DispatchQoS
public static let `default`: DispatchQoS
public static let userInitiated: DispatchQoS
public static let userInteractive: DispatchQoS
public static let unspecified: DispatchQoS
除了直接使用 DispatchQueue.global().async
这种封装好的代码外,还可以通过DispatchWorkItem
自定义队列的优先级,特性:
let queue = DispatchQueue(label: "swift_queue")
let dispatchworkItem = DispatchWorkItem(qos: .userInitiated, flags: .inheritQoS) {
}
queue.async(execute: dispatchworkItem)
Swift 中 dispatch_time
的用法改成了:
let delay = DispatchTime.now() + .seconds(60)
DispatchQueue.main.asyncAfter(deadline: delay) {
}
相较与OC来说更易读了:
let dispatch_time = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * NSEC_PER_SEC))
在 Swift 4 中,extension
可以读取 private
变量了。
Swift 3 中,如果将主体函数的变量定义为 private
,则其 extension
无法读取此变量,必须将其改为 filePrivate
才可以。
单向区间是一个新的类型,主要分两种:确定上限和确定下限的区间。直接用字面量定义大概可以写成 …6
和 2…
例如
let intArr = [0, 1, 2, 3, 4]
let arr1 = intArr[...3] // [0, 1, 2, 3]
let arr2 = intArr[3...] // [3, 4]
String
许多要通过 .characters
进行的操作,可以直接用 String 进行操作了。
例如:
let greeting = "Hello, 😜!"
// No need to drill down to .characters
let n = greeting.count
let endOfSentence = greeting.index(of: "!")!
swift 4 为字符串片段新增了一个叫 Substring
的类型。
当你创建一个字符串的片段时,会产生一个 Substring
实例。Substring
与 String
用法相同, 因为子串和原字符串共享内存,所以对子串的操作快速而且高效。
let greeting = "Hi there! It's nice to meet you! 👋"
let endOfSentence = greeting.index(of: "!")!
// 产生 Substring 实例
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"
// `Substring` 与 `String` 用法相同
let shoutingSentence = firstSentence.uppercased()
// shoutingSentence == "HI THERE!"
但是要注意一个 Substring
保留从其生成的完整的 String
值。 当您传递一个看似很小的 Substring
时,这可能导致意外的高内存开销。所以使用 Substring
时,最好转化为 String
.
let newString = String(substring)
\n
了!Swift 3,字符串换行要插入 \n
。
例如:
在 Swift 4 可以这样操作:
用两个 “”“
包裹起来的字符串会自动添加 \n
换行,更加直观了。注意:换行与缩进参照的是第二个 “”“
号的位置。
嗯,我觉得OK!
Swift 4 支持 Unicode 9,为现代表情符号修正了一些问题。
let family1 = "👨👩👧👦"
let family2 = "👨\u{200D}👩\u{200D}👧\u{200D}👦"
family1 == family2 // → true
居然还有这种操作~
KeyPath 是 Swift 4 新增加的数据类型。
定义两个结构体 Person
与Book
struct Person {
var name: String
}
struct Book {
var title: String
var authors: [Person]
var primaryAuthor: Person {
return authors.first!
}
}
let abelson = Person(name: "Harold Abelson")
let sussman = Person(name: "Gerald Jay Sussman")
let book = Book(title: "Structure and Interpretation of Computer Programs", authors: [abelson, sussman])
book[keyPath: \Book.title]
book[keyPath: \Book.primaryAuthor.name]
// 相当与
book.title
book.primaryAuthor.name
这里 \Book.title
与 \Book.primaryAuthor.name
就是 KeyPath.
KeyPath 可以用 .appending
拼接
let authorKeyPath = \Book.primaryAuthor
let nameKeyPath = authorKeyPath.appending(path: \.name)
// nameKeyPath = \Book.primaryAuthor.name
swapAt()
函数Swift 4 引入了一种在集合中交换两个元素的新方法: swapAt()
Swift 3 交换集合中的元素的用 swap()
var numbers = [1,2,3,4,5]
swap(&numbers[0], &numbers[1])
// numbers = [2,1,3,4,5]
Swift 4 中可以直接用
var numbers = [1,2,3,4,5]
numbers.swapAt(0,1)
// numbers = [2,1,3,4,5]
其他改动如:新的整数协议、泛型下标、NSNumber bridging等
可以参考:whats new in swift4
平常我们都会用 Instrument 的 Leaks / Allocations 或其他一些开源库进行内存泄露的排查,但它们都存在各种问题和不便,
在这个 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory,Leaks 工具查不出这类内存泄露,应用有限。
今天介绍一种简单直接的检测内测泄漏的方法:Debug Memory Graph
就是这货:
我最近的项目中,退出登录后(跳转到登录页),发现首页控制器没有被销毁,依旧能接收通知。
退出登录代码:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:[NSBundle mainBundle]];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginVC"];
很明显发生了循环引用导致的内测泄漏。
接下来就使用 Debug Memory Graph 来查看内测泄漏了。
首先启动 Xcode 运行程序。
点击 Debug Memory Graph 按钮后,可以看到红框内的是当前内存中存在的对象。其中,绿色的就是视图控制器。
这样,我们随时都可以查看内测中存在的对象,换句话说,就是可以通过观察 Memory Graph 查看内测泄漏。
继续运行你的程序
然后对App进行调试、push、pop 操作,再次点击 Debug Memory Graph 按钮。那些该释放而依旧在内测中的 控制器
或 对象
就能一一找出来了。
接下来,只要进入对应的控制器找到内测泄漏的代码就OK了,一般是Block里引用了 self
,改为 weakSelf
就解决了。
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self;
WS(weakSelf)
sView.btnBlock = ^(NSInteger idx){
[weakSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:idx] withRowAnimation:UITableViewRowAnimationAutomatic];
};
就这样,利用 Debug Memory Graph,可以简单快速的检测内测泄漏。
一般由两个对象循环引用的内测泄漏是比较好发现的,如果是由三个及其三个以上的对象形成的大的循环引用,就会比较难排查了。