Quantcast
Channel: 落格博客
Viewing all 377 articles
Browse latest View live

WordPress 正文自动添加版权和原文链接

$
0
0

落格博客阅读完整排版的WordPress 正文自动添加版权和原文链接

这几日博客文章又被别人拿去抓取了,虽然后来联系站长沟通解决,不过我还是要反思一下自己,文章正文中没有添加本文链接,导致即使被人抓取也无法拿到pingback……

插件法

总之,搜索了一通,似乎以前有一个插件可以一步到位(我恰好是那种喜欢一大堆插件的人),叫“Add Post URL”,不过这个插件已经下架了。

这个办法行不通,只好尝试代码级修改了:

改正文模板法

这个办法要求你的主题比较“原始”,或者你对 PHP 语言比较熟悉,原理是找到 wordpress 主题里的 single.php 文件,在里边 get_post() 函数下方插入对应的声明和链接即可,但对我来说,我主题里这部分直接是代码,我对 PHP 语言不熟悉,这个办法也不行。

代码添加插件

最终,我在 http://www.shouce.ren/api/view/a/10433 找到了办法,给 functions.php 添加代码,为文章正文末尾注入版权声明和链接,这样数据库中的文本也不会充斥大量类似内容:

add_filter ( 'the_content', 'wp_copyright' ); // 文章末尾增加版权
//文章末尾加版权声明函数
function wp_copyright($content) {
	if (is_single ()) {
		 $content .= '<p>本文由 落格博客 原创撰写:<a href="'.get_bloginfo('url').'">'.get_bloginfo('name').'</a> &raquo; <a href="'.get_permalink().'">'.get_the_title().'</a></p><p>转载请保留出处和原文链接:<a href="'.get_permalink().'">'.get_permalink().'</a></p>';
	}
	return $content;
}

讨论

当然,最后一项才是最重要的,我们双手合十🙏️祈祷抓取内容的站长手下留情……毕竟这么一行声明,一个正则就去掉了……

 

WordPress 正文自动添加版权和原文链接,首发于落格博客


正确使用 PIP 安装 Python 包 避免 TypeError: ‘module’ object is not callable

$
0
0

落格博客阅读完整排版的正确使用 PIP 安装 Python 包 避免 TypeError: ‘module’ object is not callable

在此之前,我一直是这样在 macOS 上安装和使用 pip 的:

sudo easy_install pip3

pip3 install my_package

后来,这个方法失效了,变成了这样:

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py

pip install my_package

终于有一天,pip 提示我它需要更新,于是:

pip3 install --upgrade pip3

...

...

TypeError: 'module' object is not callable

查询后,发现原来 Python 是要这样安装包的:

python -m pip install stdlib_list --user

对于 Python3,依旧使用

pip
 而不是
pip3
 ,也就是:
python3 -m pip install stdlib_list --user

 

最后,每次写这么长的命令很麻烦,可以设置一个别名

alias pip="python3 -m pip"
 当然还有
alias pip3="python3 -m pip"

对于我来说,我用的是 zsh,于是这个别名就写到

~/.zshrc
 即可。

 

另外,在安装包时使用

--user
 参数可以避免权限问题。

正确使用 PIP 安装 Python 包 避免 TypeError: ‘module’ object is not callable,首发于落格博客

在 macOS 上编译 Tensorflow 以开启 AVX2 和 FMA

$
0
0

落格博客阅读完整排版的在 macOS 上编译 Tensorflow 以开启 AVX2 和 FMA

在使用 Tensorflow 时,一直有一个奇怪的警告:

Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
 ,虽然不影响使用,但看着很烦,你可以用这个命令关闭它:
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
 。

不过,你有注意到吗?“

could speed up CPU computations
”……嗯?!

背景

总之,根据官方所述,Tensorflow 默认不支持这些高级功能是为了增强 Tensorflow 框架的兼容性,让它能够尽可能地在更多平台使用,从而避免不必要的编译操作。但这样的代价就是只能使用各个 CPU 平台都有的指令集,而高级命令就不能添加,毕竟不同的 CPU 平台它们有不同的高级指令集技术。

由于我使用 macOS,GPU 是 AMD,所以使用 Tensorflow 就别想用 GPU 进行加速了,用 CPU 又很慢,就只能跑测试数据(体积小),但我还是希望它能快一点。

所以 Tensorflow 提示我,针对我的 CPU 来说,有更高级的功能可以开启以加速训练——但怎么开启呢?答案肯定是用源码重新编译 Tensorflow。

AVX2 和 FMA

FMA 是现代 CPU 支持的一种高级指令集,中文叫“积和熔加运算”;

AVX 是现代 CPU 支持的“高级向量扩展指令集”,显然,这个 AVX2 的意思就是 AVX 进阶版的意思,它引入了上文的 FMA 运算,并将浮点性能提升 2 倍。

总之,如果 Tensorflow 能够直接使用这些高级功能,那训练速度一定能快上不少,接下来,我们就试试从头开始编译一个 Tensorflow。

准备工作

Python

显然,我们要用 Python 来使用 Tensorflow,所以你需要有 Python 环境:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
export PATH="/usr/local/bin:/usr/local/sbin:$PATH"
brew install python

这里我们使用 Brew 安装 Python3.

接下来是安装必要的依赖包:

pip3 install -U --user pip six numpy wheel setuptools mock 'future>=0.17.1'
pip3 install -U --user keras_applications --no-deps
pip3 install -U --user keras_preprocessing --no-deps

如果你使用 pip 有问题,不妨参考一下我的这一篇文章:正确使用 PIP 安装 Python 包 避免 TypeError: &#8216;module&#8217; object is not callable

源码

要从源码编译 Tensorflow,就肯定要下载它的源码,找到一个你喜欢的目录位置,执行列命令获取 Tensorflow 项目源码:

git clone https://github.com/tensorflow/tensorflow.git
 ,进入目录
cd tensorflow
 ,我们要切换到稳定版分枝,首先到https://github.com/tensorflow/tensorflow/releases查找最新的稳定版标签,就本文撰写之时,稳定版标签是
v2.1.0
 ,这里我们切换到稳定版:
git checkout v2.1.0
 .

Bazel

Tensorflow 是要使用 Bazel 来编译的,整体编译过程轻松简单,但安装 Bazel 却需要一点点小技巧。

首先,检查你的 Tensorflow 源代码文件

tensorflow/configure.py
 ,在其中大概 53 行的位置,有一行
_TF_MAX_BAZEL_VERSION = '0.29.1'
 ,这就是我们要用的 Bazel 版本。

请注意:Bazel 最新版是 2.0,直接使用 brew 安装 bazel 是无法编译 Tensorflow 的。

首先要确保你的 macOS 安装了最新版的 Xcode,然后执行命令:

sudo xcodebuild -license accept
 ,然后到https://github.com/bazelbuild/bazel/releases下载 Bazel 安装包,注意文件名应该是这样的:
bazel-0.29.1-installer-darwin-x86_64.sh
 。

点击上文文件名链接直接下载指定版本的 bazel 安装包,将来 Tensorflow 源代码编译要求可能会变,到时候读者请自行对应 bazel 版本号。

由于 macOS 10.15 加强了系统安全措施,所以如果你直接执行脚本, bazel 是无法完成安装的,会提示类似“bazel 是有未知开发者发布,可能有害”之类的警告,从而拒绝运行。

使用命令:

sudo spctl --master-disable
 暂时关闭这个警告,允许运行任意开发者的程序。

关闭警告后,就可以安装和使用 Bazel 了:

sh bazel-0.29.1-installer-darwin-x86_64.sh --user
 ,安装完成后使用
bazel --version
 查看,结果应该是:
bazel 0.29.1

编译 Tensorflow

最终,我们可以进行编译了,回到 tensorflow 目录,执行

./configure
 进行配置,它会询问你 Python 及其库的位置,如果你不是用 brew 安装的,那么可以用如下命令找到你 Python 的位置:
$ where python3
/usr/local/bin/python3
/usr/bin/python3

如果有多个结果,就选第一个即可。

$ ./configure
WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.29.1 installed.
Please specify the location of python. [Default is /System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python]: /usr/local/bin/python3


Found possible Python library paths:
  /usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages
Please input the desired Python library path to use.  Default is [/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages]

Do you wish to build TensorFlow with XLA JIT support? [Y/n]: n
No XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: n
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: n
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: n
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: n
Clang will not be downloaded.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]:


Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: n
Not configuring the WORKSPACE for Android builds.

Do you wish to build TensorFlow with iOS support? [y/N]: y
iOS support will be enabled for TensorFlow.

Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
	--config=mkl         	# Build with MKL support.
	--config=monolithic  	# Config for mostly static monolithic build.
	--config=ngraph      	# Build with Intel nGraph support.
	--config=numa        	# Build with NUMA support.
	--config=dynamic_kernels	# (Experimental) Build kernels into separate shared objects.
	--config=v2          	# Build TensorFlow 2.x instead of 1.x.
Preconfigured Bazel build configs to DISABLE default on features:
	--config=noaws       	# Disable AWS S3 filesystem support.
	--config=nogcp       	# Disable GCP support.
	--config=nohdfs      	# Disable HDFS support.
	--config=nonccl      	# Disable NVIDIA NCCL support.
Configuration finished

接下来的参数配置几乎无需设定,全部默认即可,我们不需要 GPU 支持(添加了也没用),不过最后有个 iOS 支持,这里我选择了 y,全部通过后,配置完成,就可以进行编译了。

使用命令

bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package
 编译 Tensorflow 安装包,默认编译命令就是针对本地 CPU 进行编译,也就是启用所有当前 CPU 支持的指令集,这样编译出来的 Tensorflow 就是加速的啦!

编译过程很慢,在我 16G 内存 2.5 GHz Quad-Core Intel Core i7 CPU 下要 36301 秒能完成,整整 10 小时(给错了参数,10小时完全浪费了)要54219.95秒才能完成,整整15个小时(15 年中款 15寸rmbp)!

INFO: Elapsed time: 54219.950s, Critical Path: 1802.92s
INFO: 22004 processes: 22004 local.
INFO: Build completed successfully, 22776 total actions

另,我在编译时看到了这个警告:

external/eigen_archive/unsupported/Eigen/CXX11/../../../Eigen/src/Core/util/ConfigureVectorization.h:300:10: warning: "Disabling AVX support: clang compiler shipped with XCode 11.[012] generates broken assembly with -macosx-version-min=10.15 and AVX enabled. " [-W#warnings]

我还以为无法支持 AVX 要再来一遍呢……但似乎并不影响最终效果。

生成安装包

使用命令

./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
 来生成
.whl
 安装包:
$ ./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
Sat Jan 18 03:59:17 AEST 2020 : === Preparing sources in dir: /var/folders/dn/p_qzcyss2m3gdnkkyl_bw_d00000gn/T/tmp.XXXXXXXXXX.MxFJDO4r
~/Downloads/tensorflow ~/Downloads/tensorflow
~/Downloads/tensorflow
/var/folders/dn/p_qzcyss2m3gdnkkyl_bw_d00000gn/T/tmp.XXXXXXXXXX.MxFJDO4r/tensorflow/include ~/Downloads/tensorflow
~/Downloads/tensorflow
Sat Jan 18 03:59:32 AEST 2020 : === Building wheel
warning: no files found matching 'README'
warning: no files found matching '*.pyd' under directory '*'
warning: no files found matching '*.pd' under directory '*'
warning: no files found matching '*.so.[0-9]' under directory '*'
warning: no files found matching '*.dll' under directory '*'
warning: no files found matching '*.lib' under directory '*'
warning: no files found matching '*.csv' under directory '*'
warning: no files found matching '*.h' under directory 'tensorflow_core/include/tensorflow'
warning: no files found matching '*' under directory 'tensorflow_core/include/third_party'
Sat Jan 18 04:00:00 AEST 2020 : === Output wheel file is in: /tmp/tensorflow_pkg

然后我们用 pip 安装它:

pip install /tmp/tensorflow_pkg/tensorflow-2.1.0-cp37-cp37m-macosx_10_15_x86_64.whl

最后,可别忘了开启之前关闭了的安全警告:

sudo spctl --master-enable

测试安装结果

$ python3
Python 3.7.6 (default, Dec 30 2019, 19:38:26)
[Clang 11.0.0 (clang-1100.0.33.16)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> t = tf.constant('logcg')
2020-01-18 20:04:43.088104: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x7fefdc983db0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-01-18 20:04:43.088134: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version

现在已经不再提示

Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
了,尝试跑个模型,终于达到了可以忍耐的时长。

:)

参考文献

 

在 macOS 上编译 Tensorflow 以开启 AVX2 和 FMA,首发于落格博客

macOS app 自动化上传发布到 AppCenter

$
0
0

落格博客阅读完整排版的macOS app 自动化上传发布到 AppCenter

之前我曾经写过macOS app 实现自动化 notarize 脚本,由于我的输入法使用微软的 HockeyApp 进行崩溃统计,所以我还需要把 app 上传到这里进行一次“发布”,好让 HockeyApp 能够收到对应版本的错误统计。

如今,微软的 HockeyApp 已经更新成了 AppCenter,自动化命令也十分友好,其实这个操作也可以加入到你的自动化脚本里边,一步到位。

/appcenter-cli

我们直接下载官方的命令行工具即可:

sudo npm install -g appcenter-cli
 ,安装好后,使用命令
appcenter setup-autocomplete
 来添加命令行自动补全功能,这样会方便很多。

然后使用命令

appcenter login
 ,这样生成的 Token 会自动保存,不需要每次上传都登录。

期间 appcenter 命令会问你一个 telemetry 什么的东西,我没搞懂是啥……0.0 反正直接启用了。

获取 App 列表

首先,你的 AppCenter 应该已经新建了这个 App 对吧?总之,如果我们要用命令发布更新,那就必须知道这个 App 对应的名称,它的格式是这样的:

owner/app
 ,但如果你直接按照 AppCenter 里显示的去写,那么很有可能你会遇到如下错误:
Error: failed to create release upload for xxx.zip

使用

--debug
 参数后得到详细错误:
...
...
/ Creating release upload...Response status code: 403
Body: {"message":"Forbidden","statusCode":403,"code":"Forbidden"}

显然,我们的操作被拒绝了,原因就是——其实你的用户名称和 App 名称可能并不是网站上显示的那个。

使用命令

appcenter apps list
 查看当前拥有的 App 列表,你就发现你的 App 很有可能叫这个名字:
$ appcenter apps list
  name/123
  name/la4-ge2-shu1-ru4-fa3
  name/la4-ge2-shu1-ru4-fa3-X
  name/pian1-hao3-she4-zhi4

……就是这么的令人绝望。

还好,我们可以用命令

appcenter apps show name/123
 来查看对应 App 的详情:
App Secret:            ca2f1f12-b072------8661-c----421e279
Description:
Display Name:          YourAppName
Name:                  123
OS:                    macOS
Platform:              Objective-C-Swift
Release Type:          Production
Owner ID:              -bef--f9-c4c0-4c8f-b787-aab4192a390f
Owner Display Name:    ---
Owner Email:           ---
Owner Name:            name
Azure Subscription ID:

在高亮的第三行你可以看到这个 App 在网站的名字,这样就能将这里的名字和你网站上一直以来自己设定的名字对应起来。

(没错,这个傻屌随机名不能改。)

上传更新和符号链接

找到了对应 App 的名字,接下来就是容易的部分了:

appcenter distribute release -f xxx.zip --silent -g Collaborators -a name/123 -b 1985

与在网页上上传不同的是,这里 AppCenter 不能再帮你自动推断 App 的 Build 版本号了,所以你还是需要再输入一次

-b 1985
 ,另外
--silent
 要求这个更新不通知用户,
-g Collaborators
 指定要发布到哪个测试组。

上传发布成功后结果类似这样:

Release 2.3.0 Beta (1985) was successfully released to 1 testers in Collaborators

接下来是上传编译链接文件以供崩溃统计之用:

appcenter crashes upload-missing-symbols -a name/123 ./symbols

直接指定包含链接文件的目录即可,appcenter 会自动遍历目录寻找所有的链接文件,然后只会上传需要的那一份,上传成功后结果类似这样:

6 symbols are needed to symbolicate all crashes
1 of these symbols were found and uploaded

这样,你编译好的更新版本就能自动发布到 AppCenter 了呢。

参考文献

 

macOS app 自动化上传发布到 AppCenter,首发于落格博客

使用 Supervisor 将你的 Python 程序变成服务

$
0
0

落格博客阅读完整排版的使用 Supervisor 将你的 Python 程序变成服务

不少朋友都是用 Flask 框架来写小服务器的,方便又快捷,还能一键运行,很舒服。不过,如果你真的想把你的服务部署到服务器上,那你就会发现其实还有一些功课要做,这篇文章里,我们主要来谈谈如何将写好的程序在服务器上变成一个服务而不是终端里执行的命令。

如果我们用 Python 写了一个网站应用,那么它就需要一直运行来监听 HTTP 请求,如果在终端直接执行

python main.py
 那就太麻烦了,你还得一直保持终端开启,端了 ssh,程序也就结束了。

起初,我是这么做的:

nohup python -u /home/logcg/main.py > /home/logcg/out.log 2>&1 &
 ,Nohup 这个工具能让程序在后台运行,这样即使你断开了 ssh,关闭了终端,服务器上的程序依旧在运行,如果你把这条命令写到
rc.local
 ,那么它就能每次跟随服务器启动而启动,完美……吗?

这个解决方案看上去很完美,类似的还有比如 screen 等命令,但仔细一想,如果程序实行意外崩溃了怎么办?

——很遗憾,没办法,唯一的办法就是等你的客户告诉你:你的网站打不开了。

总之,现在我们可以用另一个工具直接将你的程序“变成”系统级服务,就像 Nginx,像 fail2ban 那样,一旦程序崩溃,系统会负责将它重启——除非你手动停止它。

Supervisor

这是一个纯 Python 实现的工具,它是一个轻量级进程管理工具,能够根据你的配置自动启动并监控进程,一旦程序意外崩溃,它就会重新启动它们。

apt install supervisor
service supervisor restart

启动后,在

/etc/supervisor/conf.d
 目录下写入你的命令相关配置,最好是一个命令一个文件,方便管理,文件名要求是
.conf
 后缀。

比如,我有一个这样的服务:

[program:logcg]
command=gunicorn wsgi:app -c gunicorn.config.py  ; 被监控的进程路径
directory=/home/logcg/               ; 执行前要不要先cd到目录$
autostart=true                ; 随着supervisord的启动而启动
autorestart=true              ; 自动重启。。当然要选上了
startretries=10               ; 启动失败时的最多重试次数
exitcodes=0                   ; 正常退出代码
stopsignal=KILL               ; 用来杀死进程的信号
stopwaitsecs=10               ; 发送SIGKILL前的等待时间
redirect_stderr=true          ; 重定向stderr到stdout
stdout_logfile=/home/logcg/logfile.log        ; 指定日志文件
; 默认为 false,如果设置为 true,当进程收到 stop 信号时,会自动将该信号发给该进$
stopasgroup=true             ; send stop signal to the UNIX process
; 默认为 false,如果设置为 true,当进程收到 kill 信号时,会自动将该信号发给该进$
killasgroup=true             ; SIGKILL the UNIX process group (def false)

这样,我的 Python 程序就变成了一个叫做“logcg”的服务。

最后,

supervisorctl update all
 更新所有服务的配置信息:
➜  supervisor supervisorctl update all
logcg: added process group

当然,你还可以使用命令

supervisorctl status all
 来查看所有服务的运行状态,将
all
 换成特定的服务名称,就能查询单个的服务了。

这下,再也不用担心因为一些意外的小问题导致程序崩溃再也起不来了。

参考文献

使用 Supervisor 将你的 Python 程序变成服务,首发于落格博客

Swift 使用 strftime 快速格式化时间和日期

$
0
0

落格博客阅读完整排版的Swift 使用 strftime 快速格式化时间和日期

处理时间和日期是软件开发中很常见的操作,几乎所有的编程语言都提供了对应的 API 来方便开发者对时间日期进行处理,Swift 也不例外,比如

DateFormatter
 ,使用它你可以将任意
Date
 转换成自定义格式的文本字符串。

——但是,

DateFormatter
 很慢!它是一个非常耗时的操作,从创建到使用,每一步都占用了大量的时间,如果不是必要,几乎所有的高级程序员都不会建议你使用这个对象。

老生常谈的 C 语言

Swift 是可以直接调用 C 语言的,除了 Foundation 里的

DateFormatter
 ,其实你还可以选择C
strftime
 ,它比起前者少了不少高级功能,但如果你的需求仅仅是格式化输出时间,那
strftime
 将非常好用。

优点(相对

DateFormatter
 来说):
  • 速度快,同样的操作
    DateFormatter
     要 1.4 ms 完成,而
    strftime
     只需要0.2 ms,甚至更快!
  • 内存占用小,由于没有
    DateFormatter
     那样复杂的功能,非常节约内存资源。

缺点:

  • 代码上可能没
    DateFormatter
     那么容易操作,毕竟是 C 语言 API,即使是用 Swift 来调用,也会非常痛苦;
  • 不支持时区,最多能获取当前时区和 utc 时区;
  • 格式化操作比较少,常用的都有,但如果要
    DateFormatter
     那么多的格式,可能就需要你自己动手处理了。

代码

好在,复杂的代码操作也只需要一次性封装即可,通常与服务器通讯或者为用户显示时间,也不需要复杂的时区操作,所以绝大部分的业务场景是可以用

strftime
 代替
DateFormatter
 的。

我们写一个

Date
 的 extension 来封装这个操作:
extension Date {
    
    func formattedTime(format: String) -> String? {
        let resultSize = format.count + 200
        var result = [Int8](repeating: 0, count: resultSize)
        var currentTime = time(nil)
        var time = localtime(&currentTime).pointee
        guard strftime(&result, resultSize, format, &time) != 0 else {
            return nil
        }
        return String(cString: result, encoding: .utf8)
    }
}

这里有些困难的地方在于需要使用 C 指针,但 Swift 又把指针隐藏的很好,总之,我们使用一个

Int8
 的数组来保存返回的 C 字符串,最终再将它转换成 Swift 里的字符串返回。

格式化

代码写好了,就剩下格式问题,

strftime
 和
DateFormatter
 的格式不一样,后者通常是直接使用对应的宏,比如
yyyy
 代表了四位数字的年份,而
yy
 是两位数字的年份,前者则使用的占位符,
%Y
 等同于
yyyy
 ,
%y
 则等同于
yy
 ……

strftime
 的完整符号列表见 这里(英文),这里(中文)

使用

接下来就可以使用一行代码实现格式化时间日期输出了:

print(Date().formattedTime(format: "%y|%Y|%m|%d|%I|%H|%M|%S|%w"))

😎

参考文献

  • https://github.com/aciidb0mb3r/SPM-PkgConfig/blob/master/Sources/POSIX/strftime.swift
  • https://www.runoob.com/python/att-time-strftime.html
  • https://wiki.jikexueyuan.com/project/c/strftime.html
  • https://stackoverflow.com/questions/30932465/how-to-format-string-in-swift-using-strftime

Swift 使用 strftime 快速格式化时间和日期,首发于落格博客

Swift Debug EXC_BAD_ACCESS in AppDelegate

$
0
0

落格博客阅读完整排版的Swift Debug EXC_BAD_ACCESS in AppDelegate

 

不少人在开发中都会遇到 EXC_BAD_ACCESS ,很遗憾,这一次 Xcode 不会给出任何详细的解决方案。

通常来说,这是由于内存错误造成的。简单来说就是你创建了对象 A,但在后来访问的时候,内存里 A 这块区域已经被系统挪做他用了,比如放了对象 B 在这里——你的 A 只剩下指针,实际内容已经不存在了。

这时就会出现类似这样的崩溃:

-[__NSCFType dismissAuxiliaryWindows]: unrecognized selector sent to instance 0x6000030010e0

由于实际对象已经变更,Swift 编译器却并不知道,结果自然就是一个“未知的 Selector”了。

尝试解决它

一开始,无知的我想要去找到这个“instance”到底是谁,于是我开始用这个方法打印程序里的对象:

var a = 1
print(Unmanaged.passUnretained(a).toOpaque())

嗯,后来我发现这是一个无止境的工作……但报错变得有意思了:

-[_TtGCs23_ContiguousArrayStorageSS_$ dismissAuxiliaryWindows]: unrecognized selector sent to instance 0x600003004240

这让我更加摸不着头脑。

再后来,干脆就没报错了,就是

EXC_BAD_ACCESS
 ,于是内存地址对比也就到此为止了……

正确的做法

总之,Xcode 还是有工具来处理这种情况的——虽然不一定是百发百中,但至少能增加一点排错的线索,我们到 Xcode 左上角选择 Edit Scheme,编辑当前程序的执行选项:

选择 Edit Scheme... 来编辑运行参数
选择 Edit Scheme… 来编辑运行参数

在打开的页面选“Run”,选右侧“Diagnosis”选项卡,勾选下方的“Zombie Object”。

开启 Zombie Object 模式

Zombie Object 模式:僵尸模式,在这个模式下你的程序不会真的释放需要被释放的内存,即使对象 A 已经没有引用,它也会被保留在内存当中,这样就避免了系统将其他对象的内存写到同一位置,一旦

EXC_BAD_ACCESS
 发生,Xcode 将能够发现程序原本想要访问的是哪个对象。

再次运行程序,触发崩溃,我们得到了新的报错:

*** -[NSSpellChecker dismissAuxiliaryWindows]: message sent to deallocated instance 0x600003010b40

显然,这次就明确多了,虽然不知为何,但这次的

EXC_BAD_ACCESS
 是由于程序访问了一个
NSSpellChecker
 对象导致的,这下就大大缩小了排错范围,运气好的话应该很快就能定位问题所在了。

总之,最终记得关闭这个模式,不然程序占用内存会无休止地增加下去……

参考文献

Swift Debug EXC_BAD_ACCESS in AppDelegate,首发于落格博客

Swift Python 互通 Json 数据签名

$
0
0

落格博客阅读完整排版的Swift Python 互通 Json 数据签名

最近很流行自签证书进行 HTTPS 解密,然后就有不少人通过修改苹果的内购回执实现对 App 的破解。实际上验证购买应该是 App 连接服务器,服务器来和苹果的服务器进行通信,然后将结果发送给 App 的——但不少开发者(包括个人开发者以及企业开发者)懒得去专门维护服务器,所以直接用 App 和苹果的服务器进行通信,这就给中间人攻击提供了机会。

中间人攻击,就是说当 A 和 B 进行通信时,C 对 A 假装自己是 B,对 B 假装自己是 A,这样通信过程就从 A <—> B 变成了 A <–> C <–> B, 此时 A 和 B 都还以为对方是可信的,可通信的内容早不知道被 C 改了多少了。

通常来说我们使用 HTTPS 加密传输就已经能够很好的对抗中间人攻击了,但对于这种特殊情况,用户主动去信任一个“不安全”(自己签名)的证书进行 HTTPS 解密,那 HTTPS 加密在客户端就成了不可信的。

这种情况下,我们就需要自己实现对数据的保护。

安全性和完整性

对数据进行保护,主要是两点,“安全性”和“完整性”,前者保护数据不被第三方窥探,后者则保证你的数据在通信的过程当中不会被篡改,当然,通常来讲加密也一定程度保证了数据的完整性,毕竟如果被篡改了,可能也就无法解密了。

但只依靠加密,就给中间人攻击提供了机会。所以,我们要在 HTTPS 的安全性前提下,给自身代码添加完整性验证。

签名

所谓“签名”,和现实中的签名不同,这里的签名是指通过一些特殊的算法,将数据的特征提取出来,算法保证了一旦数据被改变,哪怕是一个符号,那么提取的特征就完全不同了,这样,我们只要在发送数据时包含这个特征,在收到后重新验证它,就能确保数据在传输过程中没有被篡改。

重签名

有一个问题就是如果中间人在改了数据之后重新签名,我们假定选择的特征提取算法被泄露了,中间人在更改了数据之后使用相同的算法重新签名(也就是提取特征),那客户端依然会认为数据是完整的——所以我们要在客户端和服务端约定一个“盐”值,你也可以理解为密码。

它不能让你把数据从特征值里恢复出来(都说了是提取特征,并不是加密,这个过程是不可逆的),但双方约定了密码之后在签名时将这个“密码”追加或者前置在数据中,这样一旦中间人更改了数据,他即使可以使用相同的算法重新签名,由于不知道我们预先设置的密码,他签出来的结果还是不同,这样客户端就知道这个数据是被篡改过的了。

重放攻击

既然无法对数据进行修改,那么骇客还可以抓取原本生效的数据报,重新再来发送,这样服务器或者客户端验证了签名和加密,数据完全正确,但实际上却是来自另一个版本客户端的传输数据——要应对这个情况,就要在之前数据结构的基础上,再增加一个概念——“时间戳”,通常我们使用 utc 的时间戳,其实就是从格林威治时间1970年01月01日到现在的总计秒数——当然你也可以用其他的能精确到秒(甚至毫秒)的时间格式——把时间戳也追加在数据里一起进行签名,避免时间戳被篡改,然后客户端和服务端进行通信时,检查时间戳是否在允许范围内,比如1秒内,或者30秒内等等,如果间隔太久,即时签名验证通过,也不能信任这个数据了,这样,就避免了重放攻击。

代码

签名的算法有很多,比如常见的 MD5、SHA1 等等等等,这些算法都可以直接拿来签名使用,这里我使用了 RSA 证书签名,实际上就是用更长更复杂的密码。

我们把要传输的数据和时间戳放在一起生成一个字符串,然后对字符串进行 SHA256 提取特征,然后将特征用 RSA 证书私钥签名;

等到客户端收到数据,就将数据和时间戳用相同的方式生成字符串,进行 SHA256 提取特征,然后用公钥进行签名验证。

这样公钥私钥的配对,好处在于,不像约定密码那样容易泄露——毕竟密码就写在程序中,很容易被找到——公钥可以随意传播,它的作用仅仅是验证签名而不能对数据进行签名,无需担心泄露问题。

生成密钥

打开任意你喜欢的终端,输入命令

openssl
 ,然后再输入命令
 genrsa -out private.pem 1024
 来生成私钥,后边的数字越大就越安全,但你也要权衡签名的时间成本,通常来说,1024 足够了。

最后输入命令

rsa -in private.pem -pubout -out public.pem
 生成公钥,这个公钥就是你要放在客户端中分发到最终用户手中的密钥了。(使用命令
exit
 来退出 openssl)

实际上,生成的密钥文件其实就是一个纯文本文档,密钥本质上是二进制的,但它们都被 base64 编码成文本保存。

使用命令

cat private.pem
 来查看你的私钥:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDKoeRzRVf8WoRSDYYqUzThpYCr90jfdFwTSXHJ526K8C6TEwdT
UA+CFPQPRUg9jrYgFcown+J2myzO8BRLynD+XHb9ilLb49Mqk2CvDt/yK32lgHv3
QVx14Dpb6h8isjncSF965fxBxlHGbvPwnHkJ9etRIYdYV3QpYohFszH3wQIDAQAB
gjIJ/dmBAkEA0QarqdWXZYbse1XIrQgBYTdVH9fNyLs1e1sBmNxlo4QMm/Le5a5L
XenorEjnpjw5YpEJFDS4ijUI3dSzylC+QQJARqcD6TGbUUioobWB4L9GD7SPVFxZ
AoGAFhKqkw/ztK6biWClw8iKkyX3LURjsMu5F/TBK3BFb2cYe7bv7lhjSBVGPL+c
TfBU0IvvGXrhLXBb4jLu0w67Xhggwwfc86vlZ8eLcrmYVat7N6amiBmYsw20GVi
LhF7zbYPIPGbHw+crP13THiYIYkHKJWsQDr8SXoNQ96TQsInTXUAmF2gzs/AwdQg
UFmePbo1G2BXqMA43JxqbIQwOLZ03zdw6GHj6EVlx369IAECQQD4K2R3K8ah50Yz
c3+EgcxRoO4bNuCFDA8VO/InP1ONMFuXLt1MbCj0ru1yFCyamc63NEUDAQJBALt7
PjGgsKCRuj6NnOcGDSbDWIitKZhnwfqYkAApfsiBQkYGO0LLaDIeAWG2KoCB9/6e
lAQZnYPpOcCubWyDq4ECQQCrRDf0gVjPtipnPPS/sGN8m1Ds4znDDChhRlw74MI5
FydvHFumChPe1Dj2I/BWeG1gA4ymXV1tE9phskV3XZfa
-----END RSA PRIVATE KEY-----

使用命令

cat public.pem
 来查看公钥:
-----BEGIN PUBLIC KEY----- 
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKoeRzRVf8WoRSDYYqUzThpYCr 
90jfdFwTSXHJ526K8C6TEwdTUA+CFPQPRUg9jrYgFcown+J2myzO8BRLynD+XHb9 
ilLb49Mqk2CvDt/yK32lgHv3QVx14Dpb6h8isjncSF965fxBxlHGbvPwnHkJ9etR 
IYdYV3QpYohFszH3wQIDAQAB 
-----END PUBLIC KEY-----

Python

 

为了方便使用,我们直接把上面私钥的代码复制,用一个变量来保存这个私钥:

#私钥文件
priKey = '''-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDKoeRzRVf8WoRSDYYqUzThpYCr90jfdFwTSXHJ526K8C6TEwdT
UA+CFPQPRUg9jrYgFcown+J2myzO8BRLynD+XHb9ilLb49Mqk2CvDt/yK32lgHv3
……
lAQZnYPpOcCubWyDq4ECQQCrRDf0gVjPtipnPPS/sGN8m1Ds4znDDChhRlw74MI5
FydvHFumChPe1Dj2I/BWeG1gA4ymXV1tE9phskV3XZfa
-----END RSA PRIVATE KEY-----'''

然后是签名代码:

def sign(data):
    key = RSA.importKey(priKey)
    h = SHA256.new(data.encode('utf8'))
    signer = PKCS1_v1_5.new(key)
    signature = signer.sign(h)
    return base64.b64encode(signature).decode('utf8')

这里

sign()
 直接传入字符串即可,它会将你传入的字符串转换成 UTF-8 编码的二进制数据进行 SHA256 摘要,然后将摘要进行签名,最终将签名结果转换为 base64 编码的字符串输出。

Swift

这里我们同样,将公钥保存到变量中,注意只复制 base64 编码部分,不要开头和结尾的

-----BEGIN PUBLIC KEY-----
 和
-----END PUBLIC KEY-----
 :
private var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKoeRzRVf8WoRSDYYqUzThpYCr90jfdFwTSXHJ526K8C6TEwdTUA+CFPQPRUg9jrYgFcown+J2myzO8BRLynD+XHb9ilLb49Mqk2CvDt/yK32lgHv3QVx14Dpb6h8isjncSF965fxBxlHGbvPwnHkJ9etRIYdYV3QpYohFszH3wQIDAQAB"

上文中输出的密钥都是一行一行的,这里我们将它们无缝拼接在一起,形成一个长串。

记得引用框架

import CommonCrypto

然后我们写一个函数来生成二进制公钥:

private func getPublicSecKey() -> SecKey? {
    let keyBase64 = Data(base64Encoded: publicKey, options: .ignoreUnknownCharacters)! as CFData
    
    let sec = SecKeyCreateWithData(keyBase64, [kSecAttrType: kSecAttrKeyTypeRSA, kSecAttrKeyClass: kSecAttrKeyClassPublic] as NSDictionary, nil)
    
    return sec
}

最终来对数据进行签名验证:

func sign(content:String, sign:String) ->Bool {
    let sha256Data = sha256(data: content.data(using: .utf8)!)
    let encryptedData = Data(base64Encoded: sign)
    let pubSecKey = getPublicSecKey()
    let verified = SecKeyVerifySignature(pubSecKey!, .rsaSignatureDigestPKCS1v15SHA256, sha256Data as CFData, encryptedData! as CFData, nil)
    return verified
}

只要传入被签名的字符串,再传入签名的 base64 编码,函数就会自动对字符串进行 SHA256 摘要,然后签名恢复成二进制数据,最后通过系统内置算法,使用你的公钥,对摘要和签名进行验证,验证通过,返回

true
 。

后记

使用不同的代码在不同的平台进行互通是一件很困难的事情,简单的加密算法和方式往往会在不同的平台得出不一样的结果,同一个语言,在同一个平台,是最容易的,但不同的语言就很复杂,具体实现时一定要注意这个问题。在尝试了无数种方式后,最终我还是使用 RSA 签名实现了跨语言平台互通,希望这篇文章对你有用。

参考文献

Swift Python 互通 Json 数据签名,首发于落格博客


当 Mailgun 不再免费,你如何安置你的域名邮箱?

$
0
0

落格博客阅读完整排版的当 Mailgun 不再免费,你如何安置你的域名邮箱?

之前我写过一篇文章:《 使用 Mailgun 创建你的免费域名邮箱 》Mailgun 有一个很有意思的功能就是收件路由,通过这个功能设定,你可以将任意发来的邮件转发到你设定好的邮箱当中,这样就不需要为每一个域名设置一个邮箱服务了,只需要通过 Mailgun 就可以将对应的邮件转发的需要的邮箱——毕竟,平时我们可能也就使用这些域名邮箱做个基本的验证。

最近,Mailgun 不再免费了,免费用户改为了按量付费的账户,一封邮件 0.0008 美元,同时,收件路由功能变为高级用户功能——这就意味着,如果你还想用 Mailgun 当域名邮箱使用,那你就需要购买至少 $35 每月的入门级会员了。

对一个月、可能半年也不需要收一封邮件的我来说,这个价格实在是难以接受。

ImprovMX

Mailgun 确实无法再用后,我花了一些时间,找到了全新的解决方案,且比 Mailgun 的方案更稳定和简单——这家网站就是专门做域名邮箱转发的!

免费额度

ImprovMX 注册即可使用,免费用户最多可同时绑定 5 个域名,每个域名最多支持 10 个邮箱地址转发,完全够用!

配置方法

收信

注册后直接添加域名进行绑定即可,在你对应的域名下添加 MX 解析

mx1.improvmx.com
 和
mx2.improvmx.com
 ,然后再添加 TXT 解析
v=spf1 include:spf.improvmx.com ~all
 即可,等待变更生效,在 ImprovMX 里点击重新验证就可以了,一旦验证通过,域名邮箱转发就已经正常工作了:
一旦域名变更生效,域名邮箱转发也就可用了
一旦域名变更生效,域名邮箱转发也就可用了

点击右侧的“

TEST
 ”按钮可发送一封测试邮件,通过检查收到的测试邮件的收件人地址可确认转发生效:
域名邮箱成功收到邮件并转发到了我的Gmail
域名邮箱成功收到邮件并转发到了我的Gmail

发信

同样的,有些朋友可能真的是在使用自己的域名邮箱作为日常邮箱的,那就需要有发信功能。ImprovMX 并不直接提供发送邮件功能,但他家支持通过 Gmail 设置发送

首先你的 Gmail 要开启两部验证——因为设置过程需要用到 App 专用密码。

开启两部验证后,到这里生成 App 专用密码,然后和之前 Mailgun 方案类似,到 Gmail 设置界面的“Accounts and Import”,找到我们之前设置的邮箱别名(如果没有,就现场添加,区别是新添加的在成功后会给你发送一封邮件用于验证),点击编辑信息。

⚠️ 注意:这里要取消勾选 “treat as an alias”。

服务器地址填写:

smtp.gmail.com
 , 端口号选
587
 ,登录账号是你当前正在使用的这个 Gmail 的邮箱地址,密码则写刚刚生成好的一次性密码,加密方式还是 TLS 不变。

保存生效即可,这样,你就可以使用这个邮件地址进行发信了,同时,对方回复的邮件也会转发回你的 Gmail 邮箱(如果你转发的是同一个 Gmail 的话)。

当 Mailgun 不再免费,你如何安置你的域名邮箱?,首发于落格博客

iOS 正确设置 status bar style 颜色

$
0
0

落格博客阅读完整排版的iOS 正确设置 status bar style 颜色

在开发 iOS App 时,很多应用都要根据当前 App 内容颜色来设置 iOS 系统状态栏 的配色,黑色或者白色——尤其是在 iOS 13 系统支持了黑色模式后。

通常,别人会告诉你这么做:

UIApplication.shared.statusBarStyle = .default

但这会触发警告:

Deprecated in iOS 9
 。那么有没有其他办法呢?有,官方推荐的写法是这样的:
override var preferredStatusBarStyle: UIStatusBarStyle { 
  return .lightContent 
}

这个要在你的 ViewController 里才可以……但你绝望地发现,它根本没有被调用。

于是你继续搜索……

在你的

Info.plist
 里,
View controller-based status bar appearance
 必须设置为
YES
 ,否则就只能在 Xcode 的 target 中设置状态栏的黑白配色。

但是……

preferredStatusBarStyle
 依旧没有被调用。

这到底是怎么回事?

这实际上是由于

UINavigationController
  导致的,由于它通常是 ViewController 里的最高层级,它不会将
preferredStatusBarStyle
 请求向下传递,相反,它会根据自身的
UINavigationBar.barStyle
 属性来决定系统状态栏到底是黑色还是白色,如果
barStyle
 是
.black
 ,那么系统栏就是
.lightContent
 也就是白色;但如果
barStyle
.default
 ,那么系统栏就也是默认的黑色了。

所以,如果你想改变系统栏颜色,在

UINavigationController
 下的 ViewController 就需要修改
UINavigationBar.barStyle
 而不是重写
preferredStatusBarStyle
 。

另外

如果你是在 iOS 13 以上版本的 iOS 中这么做,你会发现系统状态栏还是不变……所以你可能需要在你的

Info.plist
 里加上
UIUserInterfaceStyle
 字段,并设置值为
Light
 ,这样可以让你的 App 不配适 iOS 13 的黑色模式——因为如果配适了的话,在黑色模式下,你的系统状态栏将永远是白色,不会变色……

参考文献

iOS 正确设置 status bar style 颜色,首发于落格博客

避免 WordPress 被用作反射放大攻击

$
0
0

落格博客阅读完整排版的避免 WordPress 被用作反射放大攻击

之前我写过一篇文章,使用 fail2ban 防止 Bind9 被用于 DNS 放大攻击,万万没想到,原来 WordPress 本身也可以用来进行放大攻击,原理就是它的 Pingback 机制。

Pingback 是 WordPress 的一个网站之间互相通知工具,比如 A 博客引用了一个 B 博客文章的链接,那么 WordPress 就可以自动帮你通知 B 博客,告诉博主你引用了他的文章。

这本来是一个很不错的功能,但有一点——Wordpress 至今没有为这个 Pingback 做安全检测。Wordpress 在收到 Pingback 时会检测来源站是否有效——这要和源站进行通信,但它并没有检测发来 pingback 和 pingback 里携带的 ip 是否相同……

也就是说,发送上万个包含另一个网站的 ip 的 pingback 给 100 个 WordPress,那么就可以通过反射放大成 100万 次 TCP 连接给那个网站!

这不就是 DDoS 么 0.0

所以,还是关了它好了……

关闭 Pingback 和 Trackback

关闭 WordPress 的 pingback
关闭 WordPress 的 pingback

既然这个关闭了,那么平时如果你不用到 WordPress 的 XML RPC,也一起关了算了。这个 RPC 是用来给用户远程调用 WordPress 的,比如一个客户端…… 如果你用得到,就算了。

关闭 WordPress XML-RPC 服务

这个服务在早期实际上是有漏洞的,后来直到现在,实际上 XML-RPC 服务的漏洞早已经修复(令人惊讶的是 Pingback 的漏洞却一直保留着——都这么多年了……)

在你主题的 functions.php 里,加入如下语句即可关闭:

add_filter('xmlrpc_enabled', '__return_false');

禁止 xmlrpc.php 访问

如果你使用 Nginx 服务,那么在你的 nginx 站点配置中加入

location /xmlrpc.php {
    deny all;
}

这样就完全禁止这个文件的外部访问了。

后记

要不是人家找上门说我的博客被用来反射放大攻击攻击人家网站,我还真不知道这个四五年前的安全漏洞 WordPress 到现在都没有修复……说实在的这个功能还挺好用的,我因为它,抓到了好几个粗心大意偷我文章假装原创的小伙……😂

也有朋友说,不修补这个漏洞而直接禁止这个功能,就好像医生通过杀死病人来治病……是没错啦,但考虑到每次 WordPress 更新升级有可能覆盖掉这个文件(并且不一定修复这个漏洞),并且通常情况下我也不使用这个功能,就干脆关闭好了。

参考链接

避免 WordPress 被用作反射放大攻击,首发于落格博客

Safari 13 去除 Google 搜索结果跳转

$
0
0

落格博客阅读完整排版的Safari 13 去除 Google 搜索结果跳转

在 Safari 9 以前,我们只要下载一个 safariextz 文件双击它,就能让 Safari 加载这个插件了。好处肯定是很方便,但也不太安全(随处都能下载到的插件,很可能是被篡改过的)。

所以后来,Apple 就不再允许用户直接下载插件给 Safari 安装了,必须通过 Mac Apple Store 下载 App,然后 App 里以插件的形式提供 Safari 插件,配合 App 的完整性校验,这样用 Safari 插件就安全多了,同时,由于和 MAS 的统一,插件也方便卖钱了(对开发者来说)。

但这样直接导致了很多 Safari 开发者无法给 Safari 提供插件——因为他们并不是专门的 Apple 生态开发者,也没有购买苹果的开发者会员,这些插件就无缘 MAS,用户又不能再像以前那样直接安装,这些(绝大多数)插件就再也不能用在 Safari 上了。

不过,你还是可以自己编译 App 来创建 Safari 插件的,毕竟本质上工作原理没有变化。

当然,由于我才疏学浅,目前也就碰巧让“屏蔽谷歌跳转”的插件生效了,我不知道是不是一个插件只能加载一个功能,还是其他插件的功能确实就无法在现在的 Safari 上实现……

准备

首先你需要下载最新版本的 Xcode,不需要购买开发者会员,但你应该有一个免费账户,就是你的 Apple ID。

然后是你想要加载的插件,也就是 safariextz 文件。

编译程序

首先,把你的

myplugins.safariextz
 文件改名为
myplugins.zip
 ,这样你就可以把它解压缩了。

解压缩后在插件里找到一个

.js
 文件,这就是我们稍后要用的插件代码,请保存好。

使用 Xcode 创建一个 Safari 扩展项目:

使用 Xcode 创建一个新的 Safari 插件项目
使用 Xcode 创建一个新的 Safari 插件项目

创建好的空项目就已经是新版一个 App 加一个扩展的样子了。

接下来,在左侧导航到插件的子项目中,找到它的

Info.plist
 ,在里边找到
Allowed Domains
 ,将里边的内容改为
*.google.com
 :
修改插件配置
修改插件配置

然后在左侧文件列表找到

script.js
 这个文件,删除里边的内容,然后把之前在插件里找到的那个
.js
 文件内容全部复制粘贴到这里边。

激活插件

好了,现在我们就来运行这个程序,注意,是 App,不要直接运行那个插件子项目。

运行后你会看到一个模板 App 启动:

直接启动 App
直接启动 App

点击窗口中唯一的按钮,系统会自动跳转到 Safari 扩展设置界面,勾选我们刚刚创建的这个插件即可。

这里我给插件起名为“GR”,各位请根据你创建插件时自己起的名字对号入座 :)

插件一旦加载到 Safari,你的 App 就可以退出了,Xcode也可以关闭,甚至项目文件也能删除。

将 App 保存下来

由于直接启动程序其实是在 Xcode 的编译缓存中运行的,虽然你不主动清空,它就会一直存在,但我们还是把它放到正常的目录中吧。

在 Safari 的插件管理界面,选择 “卸载”,Safari 会告诉你需要去删除这个插件对应的 App,然后点击“在 Finder 中显示”,你就找到了这个 App 的位置,把它拖到 “Applications” 目录中吧,然后重新打开它加载一次即可。

 

 

Safari 13 去除 Google 搜索结果跳转,首发于落格博客

Python 实时检测自身内存占用

$
0
0

落格博客阅读完整排版的Python 实时检测自身内存占用

最近在做文本统计,用 Python 实现,遇到了一个比较有意思的难题——如何保存统计结果。

直接写入内存实在是放不下,十几个小时后内存耗尽,程序被迫关闭。如果直接写入数据库吧,每次写入又太慢了,本来就十几个小时了,这样下去就要往星期上数了,也不是个办法。

最后,我想到了一个两者兼顾的方案——用内存做缓冲,达到一定量之后一次性将当前所有数据合并到硬盘里。

但这样就有一个阈值,如何确定同步硬盘的时机,通常可以按照文件粒度进行处理,比如处理一个语料文件同步一次……但我的语料有大有小,大的有10GB,根本等不到那一刻内存就爆炸了,后来我想用统计数据量进行判断……可这又有点难以估计,小了吧频繁写入,缓存的意义就不大了,大了吧还没等到条目数量达到,内存就已经爆满。另外考虑到将来程序会运行在不同配置的设备上,让其他开发者根据自身情况计算这个阈值也有点太不友好,于是我想到了一个办法——不如让 Python 自己检测自己的内存占用,如果快满了(或者达到阈值),就同步写入硬盘一次。

对于其他开发者来说,自身设备的内存多大是很容易查看的,根据系统运行状况设置一个合理的阈值,相当方便。

要用 Python 监控自身内存占用,要使用

psutil
 这个库来和系统进行交互,基本逻辑就是先拿到自己的
pid
 ,然后根据这个
pid
 去跟系统获取进程信息。
def get_current_memory_gb() -> int:
    # 获取当前进程内存占用。
    pid = os.getpid()
    p = psutil.Process(pid)
    info = p.memory_full_info()
    return info.uss / 1024. / 1024. / 1024.

比如我系统是 32GB 内存,那么我设置个 20GB 就相当安全,用 Python 进行统计语料,数据多到进程占用 20GB 内存了,就把当前的数据写入硬盘,同步统计数据,然后清空程序里的字典缓存释放内存。

——完美。

Python 实时检测自身内存占用,首发于落格博客

clang: warning: libstdc++ is deprecated; move to libc++ with a minimum deployment target of OS X 10.9

$
0
0

落格博客阅读完整排版的clang: warning: libstdc++ is deprecated; move to libc++ with a minimum deployment target of OS X 10.9

通常,你会在安装某个 Python 包时遇到这个错误,这个包肯定是一个包含了 C++ 代码的包。

这是由于 macOS 更新后出现的不兼容,一般来说,这个包的维护者应该已经对这个特殊情况做出了兼容:

if platform.system() == 'Darwin':
    extra_compile_args += ['-mmacosx-version-min=10.7', '-stdlib=libc++']

这样在 macOS 上就可以顺利编译通过了。

但如果你使用的是 PyPy,那可能即使维护者包含了这句话,你也无法成功安装对应的包,这时就需要手动指定编译器:

sudo CFLAGS=-stdlib=libc++ pip_pypy3 install your-package

CFLAGS=-stdlib=libc++
 指明要使用的编译器,避免 PyPy 忽略
setup.py
 中的某些设定信息。

clang: warning: libstdc++ is deprecated; move to libc++ with a minimum deployment target of OS X 10.9,首发于落格博客

使用 xcode-install 来管理 Xcode 版本

$
0
0

落格博客阅读完整排版的使用 xcode-install 来管理 Xcode 版本

我们都知道,在使用 Python 的时候有 pyenv,使用 ruby 的话有 rbenv,都能方便地控制和管理编程语言环境的版本,那么,有没有什么方法,能让 Xcode 也像这样切换版本呢?

每次 Xcode 发布更新,更还是不更成了一个问题,下载难,还不能断点续传,又不能用第三方工具下载,真的是非常要命……就更别提多个版本时候的兼容问题了。

使用 xcode-install,一次性解决这些问题,耶!

它是一个专门用来管理 Xcode 版本的工具,可以一键切换不同版本的 Xcode,在你的 Mac 上安装多个版本的 Xcode,非常方便。

首先,使用命令安装它:

gem install xcode-install
 ,

然后是更新可用版本列表:

xcversion update
 ,不过由于是要从苹果开发者网站拉取所有可用版本列表,所以需要你的开发者登录账号和密码,这些会保存在本地 iCloud 钥匙串当中,不用担心。

输入后你很有可能会遇到如下报错:

.rbenv/versions/2.7.0/lib/ruby/2.7.0/rubygems/version.rb:215:in `initialize': Malformed version number string Xcode (ArgumentError)

这是由于你当前已经安装了 Xcode 导致的,这阻止了 xcode-install 创建链接,所以,根据你当前安装的 Xcode 版本,将现在已经安装的版本改名,比如我现在安装的版本是最新的 11.5,那么就把

Xcode.app
 改名为
Xcode-11.5.app
 ,然后再重试即可。

更新列表后就可以根据列表中的版本号进行安装了,你可以看到,在版本列表中,11.5 的版本已经安装:

11.4.1
11.5 beta
11.5 (installed)
11.5 GM Seed
11.5 beta 2

就使用命令

xcversion select 11.5
 让当前 Xcode 生效。

使用

xcversion selected
 来验证:
Xcode 11.5
Build version 11E608c

现在 xcode-install 已经能正确识别我们原本安装且正在使用的 Xcode 版本了。

注意如果版本号里包含空格,比如测试版,那么需要用单引号将版本号括起来,使用命令安装另一个版本的 Xcode:

xcversion install '12 beta'


当然,这个工具是使用 curl 进行下载,其实延时等待时间没什么特殊优化,如果你用浏览器下载很容易断,那用这个工具也是一样,只是版本管理比较方便……

使用 xcode-install 来管理 Xcode 版本,首发于落格博客


/usr/libexec/lsd 占用 100% CPU 的解决办法

$
0
0

落格博客阅读完整排版的/usr/libexec/lsd 占用 100% CPU 的解决办法

最近忽然发现磁盘存在大量写入,打开任务管理器一看,发现一个叫做“lsd”的进程持续占用 20% – 40% CPU,很奇怪。

经过一番查询,得知这个进程是 macOS 和 iOS 上的系统进程,全名叫做“Launch Service Daemon”,负责所有 App 文件类型关联和启动。但它的数据库有时候会损坏,这就导致它频繁读取和验证某些数据。

一旦它的数据库损坏,你就会遇到 lsd 占用 100% CPU,或者大量内存,甚至是巨大的磁盘写入……

总之,我们可以让它重新注册所有必要的文件,重建数据库即可:

find /System/Library/Frameworks -type f -name "lsregister" -exec {} -kill -seed -r \;

或者,使用这个命令:

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user
 这个和上一句理论上执行相同任务,只是前者搜索
lsregister
 而后者直接给出了路径。

参考文献:

https://discussions.apple.com/thread/8365107?answerId=33313731022#33313731022

https://discussions.apple.com/thread/250050422?answerId=250085966022#250085966022

 

/usr/libexec/lsd 占用 100% CPU 的解决办法,首发于落格博客

使用 xcodebuild 来 archive 并导出 app

$
0
0

落格博客阅读完整排版的使用 xcodebuild 来 archive 并导出 app

之前我曾写过一篇文章macOS app 实现自动化 notarize 脚本,但并没有提到使用代码自动编译并生成 App 的脚本,毕竟这一步有好多工具可以完成,比如说 fastlane。

我由于在 notarize 之前也没想过做自动化,而在写那篇文章的时候 fastlane 还没有支持 notarized 上传,于是我就自己写了,具体的编译命令是这样的:

xcodebuild -project xxx.xcodeproj -scheme xxx  DSTROOT="./" DWARF_DSYM_FOLDER_PATH="./Applications" archive

这样就可以直接把 dsym 和 app 文件生成出来了,方便的很。

……可是好景不长,最近苹果似乎改动了 notarized 服务器行为,之前就一直让人头疼的 sparkle 集成签名问题再度出现,简而言之就是每次使用脚本生成的 app,对 sparkle 的签名总是出现各种问题,而本地检测又是正常的。

作为对比,使用 Xcode 自己的 archive 生成并上传,是一切正常的(我单独给 Sparkle 做了额外的签名代码,是可行的,但用命令编译就不行)。

我猜测可能是直接生成 App 丢失了什么缓存,经过查询,现在分为两步走,先生成正常的 archive ,然后再从 archive 中导出 app。

xcodebuild archive  -project xxx.xcodeproj -scheme xxx -configuration release -archivePath ./Applications/xxx.xcarchive

生成

xcarchive
 ,这一步没什么好说的,直接就能成功了,接下来的步骤则有点复杂:
xcodebuild -exportArchive -archivePath ./Applications/xxx.xcarchive -exportPath ./Applications/ -exportOptionsPlist ./exportOptionsAdHoc.plist -allowProvisioningUpdates

选择你刚刚生成的 xcarchive,然后从里边导出 app(并签名),这一步要求了一个奇怪的

-exportOptionsPlist
 ,且目前在网上也找不到什么完整的模板,总的来说这个配置文件是不用写满全部参数的,把主要参数写了就行,其他参数按默认即可,以前的 Xcode 版本还可以从 archive 功能里导出一份配置文件(这个文件在导出时也存在一份拷贝),现在似乎不能了,总之,我给出一个模板,你可以参考来写,反正如果写错了,它会报错,然后告诉你缺少什么参数,又或者哪个写错了,应该是哪些之一,很好排错:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>provisioningProfiles</key>
	<dict>
		<key>com.logcg.inputmethod.LogInputMac.Settings</key>
		<string>SettingsLGCloud</string>
		<key>com.logcg.inputmethod.LogInputMac2</key>
		<string>LogInputMac2</string>
	</dict>
	<key>method</key>
	<string>developer-id</string>
	<key>iCloudContainerEnvironment</key>
	<string>Production</string>
	<key>signingCertificate</key>
	<string>Developer ID Application</string>
	<key>signingStyle</key>
	<string>manual</string>
	<key>teamID</key>
	<string>---</string>
</dict>
</plist>

比如上文中

teamID
 和
signingStyle
 字段,其实都可以省略,因为
xcodebuild
 会自动从你的项目配置中读取。

这下导出的 app 就可以正常 notarized 了。

使用 xcodebuild 来 archive 并导出 app,首发于落格博客

在 macOS 上无驱动使用第三方鼠标

$
0
0

落格博客阅读完整排版的在 macOS 上无驱动使用第三方鼠标

众所周知,macOS 对第三方鼠标挑剔的很,如果是普通的办公鼠标,那几乎还能凑合用,固定的 DPI 顶多就是需要调调鼠标速度罢了,但如果上升到按键多一些的游戏鼠标,就有很多问题了。

不一定是用来打游戏,比如带有前进后退按钮的鼠标在写代码时非常实用。

一般来说,名厂大牌的鼠标会对 macOS 支持较好,主要是因为大厂有精力为 macOS 做鼠标驱动,如果一个鼠标原生就只有 Windows 驱动,那它带有的更多按键就不能很好的被 macOS 识别,花大价钱买的高级鼠标在 macOS 上成了残废。

如何买到一款能在 macOS 下蓝牙稳定连接的鼠标,成了 Mac 用户心头永远的痛。


即使是像罗技这样的大厂,他们的驱动也并不那么稳定,除了鼠标本身“用一段时间后就卡顿[1]”的问题外,还有就是驱动造成的,每间隔一段时间,鼠标设定的宏就会失效,变成了没装驱动时的样子,打开设置软件,甚至它都没有识别到鼠标,这时只能关掉鼠标,再打开重新连接,然后再继续工作。甚至,驱动服务自己也会经常性卡死,导致风火轮……

难道没有这些驱动,鼠标就真的不能用吗?为什么 macOS 总是把鼠标的侧键默认为奇怪的 expose 功能,而不是前进后退呢?

在摸索了一段时间后,我似乎找到了一个比较可行的解决方案。


第三方鼠标按键管理工具

1、用 Karabiner 修正鼠标按键识别

按键映射的工具有很多,其中不少也支持鼠标的按键映射……但难点就是鼠标的 button4 或 button5 会被系统固定识别为 expose,从而导致绝大多数鼠标按键映射工具无法识别这个按键——除了官方驱动。

使用 Karabiner-Elements 可以解决这个问题,操作方法有点 tricky,首先当然是要在 Karabiner-Elements 的 Devices 中勾选你的鼠标:

启用对罗技鼠标的管理
启用对罗技鼠标的管理

这样做之后,再到第一个选项卡, Simple modifications 中添加对鼠标 button4 或 5 的映射,虽然在 Karabiner-EventViewer 中其实也看不到鼠标 button4 的信号,但这并不妨碍你映射它……映射成 button6!(实际上,应该是映射到任意一个你鼠标上不存在的按键)

这样映射之后,你就会发现这个键不会再触发 expose 了,这是我们使用其他软件映射鼠标按键功能(当作第三方驱动)的基础。

将鼠标4键映射到不存在的6键上
将鼠标4键映射到不存在的6键上

注意,Target device 一定要选择为之前我们勾选的鼠标,否则无效。

2、使用 BetterTouchTool 等软件管理鼠标按键

修正了鼠标按键功能后,你就可以使用任意支持映射鼠标按键的工具进行映射了,再也不需要罗技官方的垃圾驱动。

除了本文提到的 BetterTouchTool 外,理论上你也可以尝试 USB Overdrive 或者 SteerMouse 这些工具都是需要付费购买授权的。其实你也可以继续使用 Karabiner,但它的配置实在是太复杂了,毕竟我们要根据不同的软件设置不同的鼠标功能(比如Xcode 和 VSCode 中,前进和后退的快捷键就是不同的,当然在浏览器中的前进后退又是另外一种按键组合)。

在 BetterTouchTool 中设置鼠标按键功能

现在这些软件应该都能正确识别鼠标的按钮了,你要做的就是按照你的需要将它们的映射设置好,如果你使用的是 BetterTouchTool,那要注意设定的模式选择为“Normal Mouse”。

 

最后,附上一个玄学设定,如果你的蓝牙鼠标卡顿,那也可能是 macOS 自己的问题,尝试到网络设置中调整网络服务排序,将“Bluetooth PAN”拖动到第一位(放心,你几乎用不到它,这么做不会影响你的任何网络功能),然后你可能就会发现鼠标不卡了……

 

最最后,如果你发现卸载鼠标驱动后鼠标部分按键失灵失效,那就需要将鼠标从 macOS 中删除然后重新配对。


[1] 我的多款蓝牙鼠标都是刚买的时候还算不错,可随着使用,几个月后就变得卡顿,经常性断开以及丢帧,很难界定是什么原因——毕竟,在 Windows 下它又是可以完全正常使用的。

在 macOS 上无驱动使用第三方鼠标,首发于落格博客

Xcode 丢失插件的解决办法

$
0
0

落格博客阅读完整排版的Xcode 丢失插件的解决办法

在之前的一篇文章中我为大家介绍了一个同时安装多个版本 Xcode 的工具使用 xcode-install 来管理 Xcode 版本但当你的系统中同时存在多个 Xcode 时,就会导致系统困惑,让 Xcode 的插件无法被识别,主要的体现就是系统偏好设置中根本看不到 Xcode 插件的选项——就好像你完全没安装过它们一样。

总之,我在这里找到了答案

首先执行命令:

PATH=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support:"$PATH"

这条命令会临时添加一个工具的路径到我们的环境中,方便执行接下来的命令:

lsregister -f /Applications/Xcode.app

这条命令触发系统重新注册 Xcode 到系统,注意,这里 Xcode 的路径必须是你的真实路径,比如我现在系统中有 Xcode-12.2.app,还有 Xcode-12.1.app,那我要这样执行命令: lsregister -f /Applications/Xcode-12.2.app

命令执行后再到系统偏好里查看,就可以看到熟悉的 Xcode 扩展启用界面了。

Xcode 丢失插件的解决办法,首发于落格博客

Cocoa Binding 实用教程

$
0
0

落格博客阅读完整排版的Cocoa Binding 实用教程

首先说这不是一个新技术,它很老,老到几乎没人提起它。

这是苹果 MVC 模式下的产物,最早在没有 iPhone 的时候就已经诞生了,它是用来配合 Xcode 图形化设置界面用的——比如 NIB,当然,现在已经变成 XIB了,哦,还有 Storyboard。

现在如果说起要 bind 一个 Storyboard 中的对象到代码中,你可能查到的都是这样的:

在左侧栏点击鼠标右键打开小窗口,然后【拉线】连接代码中的声明与实际的 UIButton……

这样在程序加载这个 Storyboard 时,你图形化创建的 Button 就自动连接到了代码的声明中,比如:

@IBOutlet var myBox:NSBox!
@IBOutlet var myButton:UIButton!

没错,通常来说我们是这么用的,但如果我这是一个“设置”界面,里边有大量的简单控件,但要根据用户当前设置状态来改变和显示(这是个很常见的情景,毕竟哪个 App 没个偏好设置呢?),大量的 @IBOutlet@IBAction 可能并不是一个好的选择,因为除了这些,你还需要在初始化代码中增加大量的配置读取代码,状态判断,然后再去更改对应控件的状态,如果选项多的话,那基本就是灾难了。

就拿落格输入法的设置界面来说,每一页都有很多的按钮控件
就拿落格输入法的设置界面来说,每一页都有很多的按钮控件

Cocoa Binding 救你命

打开 Xcode ,加载一个 Xib,选中一个控件,在右侧栏你会看到常见的属性设置界面:

Xcode 中常见的属性设置栏
Xcode 中常见的属性设置栏

你可能经常使用前边几个功能:

通常 Xcode 中常用的功能选项
通常 Xcode 中常用的功能选项

但,这个功能你肯定没用过:

Cocoa Binding 设置界面
Cocoa Binding 设置界面

如果你去网上查找,那几乎得不到什么有用的介绍——因为它很古老了。得益于 Objc 的 MVC 设计风格,实际上每一个控件都可以直接监听你对象中的变量,并自动根据它的变化来改变自身显示的,你完全不需要去初始化那个对象,也不用根据用户当前的设置来设定这个按钮是可用还是不可用,一切其实都可以是自动的,你需要提供的——不过是一个变量而已。

不过问题来了,现在已经都在用 Swift 了,怎么才能让它绑定到 Swift 代码中的变量呢?如果你声明一个变量,并用这个界面来进行绑定,那么就会遇到运行时错误:

this class is not key value coding-compliant for the key xxx

这是由于 Swift 默认不向 Objc 暴露变量名称导致的,如果任何 #selector()  调用方法一样,我们在对应的变量前加上 @objc dynamic ,比如这样 @objc dynamic var mySetting = false ,这样,就可以绑定成功了。

注意,这里我们除了常见的 @objc 外,还追加了 dynamic 修饰,如果只使用前者,则可能在第一次调用时失败, Swift 依旧会将你声明的变量优化成静态,这样 Cocoa Binding 就不能动态监听我们的绑定了。

(取决于你的编译优化,为了保证无论如何绑定都能生效,还是应该加上 dynamic 修饰)

在绑定界面,我们选中一个 Button,比如我这个按钮控件是有选中状态的那种 ☑️,所以它有开和关两种状态,于是我就可以把它的 value 进行绑定,这里要记得先设置好 Xib 的 File's Owner 一般这个默认是空的,设置好后,我们就可以选择绑定到 File's Owner 了:

绑定对象实例变量到 UI 界面
绑定对象实例变量到 UI 界面

这里要使用 KeyPath 进行设置,实际上就是你变量的名字即可。现在这个按钮的状态就会自动根据变量的值进行改变了。完全不需要任何初始化设置。

额外的,除了按钮对象,你还可以绑定 Label,显然,它的 value 就是字符串对象了。值得一提的是,如果你绑定一个数组控件……比如 NSPopUpButton 这种,它实际上是有多个数据,那么你也可以在这个设置界面的下方找到 selectedIndex 这样的状态进行绑定,值类型就是我们熟悉的 Int ,如果你直接绑定 value,那值其实是一个比较复杂的 NSArrayController。

当然,如你所见,你还可以方便地绑定一个空间的 Enabled, isHidden 等等状态,但凡是能设置的状态,几乎都能和变量进行绑定,根据情况自动发生改变——再也不用繁琐的代码来操作界面了!

进阶:ValueTransformer

设置绑定的时候,会有一个默认留空的选项, ValueTransformer ,它可以将你绑定的值进行一定的处理,比如默认的,将布尔值进行反转,判断空为 true  或者 false ,除此之外,我们还可以自定义如何对值进行处理。

比如我的设置选项有 0 1 2 三个状态,但按钮选中与否是两个,我需要将它映射到 0 和 2 上,默认的 Transformer 就无能为力了。

首先,我们要继承一个 ValueTransformer ,这样就能自定义处理流程:

class ValueTransformer2:ValueTransformer {
    override func transformedValue(_ value: Any?) -> Any? {
        guard let a = value as? Int else {return false}
        return a == 2
    }
    class override func allowsReverseTransformation() -> Bool {
        return true
    }
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let a = value as? Bool else {return false}
        return a ? 2 : 0
    }
}

其中 transformedValue(_ value: Any?)-> Any? 返回的是控件需要的值,比如我的按钮,我就设置 return a == 2 这样当且仅当值为 2 时按钮状态为启用。

allowsReverseTransformation() -> Bool 规定了这个 ValueTransformer  是否能逆向转换,默认是 false ,这里我需要逆向,因为按钮选定时应该给变量设置为 2 而不是 1 ,所以我要使用 reverseTransformedValue(_ value: Any?) -> Any? 来将按钮控件传入的值转换成变量需要的值。

写好后我们需要将这个自定义的 ValueTransformer  进行注册,否则 Swift 是找不到这个对象的:

ValueTransformer.setValueTransformer(ValueTransformer2(), forName: NSValueTransformerName("ValueTransformer2"))

我们在任何初始化界面之前的地方注册即可,注意我直接给它起名为 "ValueTransformer2" ,然后就可以在 Xcode 的 Binding 界面将 ValueTransformer 写成这个 ValueTransformer2 了,这里我故意将名称写的和类名称一致,你也可以写成其他不对应的名字。


额外地,如果你用了自己的配置管理实例,那么也可以将绑定的变量写为计算属性,这是完全没有问题的,不过值得一提的是,绑定变量的控件,是依靠监控对应变量的 didSet 来获取通知的,这是唯一触发界面更新的方式,所以,如果你使用了计算属性,那么更新了后台存储后,记得也要触发一下计算属性的 didSet ,比如

@objc var myValue:Int {
 get {your code...}
 set {do nothing}

}

myValue = 0

 

Cocoa Binding 实用教程,首发于落格博客

Viewing all 377 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>