Basic Linux Commands

作为非科班出身的技术人员, 关于Linux命令也应该掌握些基本的, 所以整理一下.

Files & Navigating

ls - list all files/folders on current directory (列出当前目录下所有文件和文件夹)

ls -l - formatted listing in line (详细列出所有文件及信息, 每个文件或文件夹占一行)

ls -la - formatted listing including hidden files (详细列出所有文件包括隐藏文件及信息, 每个文件或文件夹占一行)

cd dir - change directory to dir (改变当前目录到dir文件夹)

cd .. - change to parent directory (改变当前目录到父级目录)

cd ../dir - change to dir in parent directory (改变当前目录到父级目录下的dir文件夹)

cd or cd ~ - change to home directory (回到home目录下)

cd - - change directory to last directory (返回进入之前所在的目录)

pwd - show current directory (显示当前目录路径)

mkdir dir - create a directory named dir (创建一个名为dir的文件夹)

rm file - remove file (删除文件)

rm -f file - force remove file (强制删除文件)

rm -r dir - remove directory dir (删除文件夹)

rm -rf dir - force remove directory dir (强制删除文件夹)

rm -r * - delete all files and directories except hidden files under the current directory(删除当前目录下除隐含文件外的所有文件和子目录)

rm -rf / - launch some nuclear bombs targeting your system (删除系统….千万别这么做)

cp file1 file2 - copy file1 to file2 (将file1复制为file2)

mv file1 file2 - rename file1 to file2 (将file1重命名为file2)

mv file1 dir/file2 - move file1 to dir as file2 (将file1移动到dir并重命名为file2)

touch file - create file or update file (创建文件, 或如果文件存在的话更新文件, 如时间戳等, 但不会清空文件已存在的内容)

cat file - output contents of file (输出文件内容)

cat > file - write standard input into file (写入文件内容, 覆盖原有)

cat file1 file2 > file - combine file1 and file2 into file (将文件file1和file2合并后放入文件file中)

cat >> file - append standard input into file (在文件后追加内容)

tail -f file - output contents of file as it grows (当文件内容增加时会打印出来, 一般用于日志监控)

Networking

ping host - ping host (测试主机之间网络的连通性)

whois domain - get whois for domain (查询域名信息)

dig domain - get DNS for domain (查询DNS信息)

dig -x host - reverse lookup host (执行逆向域名查询)

wget file - download file (下载文件)

wget -c file - continue stopped download (断点续传)

wget -r url - recurively download files from url (递归下载 慎用)

curl url - outputs the webpage from url (输出页面内容)

curl -o meh.html url - writes the page to meh.html (web页面写入本地文件)

ssh user@host - connect to host as user (ssh连接user到host)

ssh -p port user@host - connect using port (ssh指定端口连接)

Processes

ps - display currently active processes (报告当前系统的进程状态)

ps aux - detailed outputs (显示所有进程详细信息)

kill pid - kill process with process id (pid) (以pid杀掉进程)

killall proc - kill all processes named proc (杀掉所有名为proc的进程)

System Info

date - show current date/time (显示当前系统日期时间)

uptime - show uptime (显示uptime)

whoami - who you are logged in as (显示当前登录用户)

w - display who is online (显示当前在线用户)

du - show directory space usage (显示当前目录下空间使用)

df - show disk usage (显示磁盘空间使用)

uname -a - show kernal config (显示内核信息)

top - show processes in a dynamic real-time view (实时显示系统各进程资源使用情况)

Compressing

tar cf file.tar file1 file2 - tar files into file.tar (合并file1, file2为file.tar)

tar xf file.tar - untar into current directory (拆解file.tar到当前目录)

tar tf file.tar - show contents of archive (显示tar文件内容)

options:

c - create archive (建立新的备份文件)
t - table of contents (列出备份文件的内容)
x - extract (从备份文件中还原文件)
z - use zip/gzip (使用gzip指令备份)
f - specify filename (指定备份文件名)
j - bzip2 compression (支持bzip2解压文件)
w - ask for comfirmation (确认压缩文件的正确性)
k - do not overwrite (保留原有文件不覆盖)
v - verbose (显示操作过程)

Permission

chmod 777 file - change permissions of file (修改文件权限)

4 - read (r)
2 - write (w)
1 - execute (x)

order owner/group/world

chmod 777 - rwx for everyone (所有人可读可写可执行)

chmod 755 - rw for owner, rx for group and world (文件所有者可读可写可执行, 其他人可读可执行)

Xcode 常用快捷键

键位

⌘: Command

⌃: Control

⌥: Option

⇧: Shift

快捷键

运行 ⌘ + R

编译 ⌘ + B

停止 ⌘ + .

当前文件查找 ⌘ + F

查找下一处 ⌘ + G

查找上一处 ⌘ + ⇧ + G

全局查找 ⌘ + ⇧ + F

快速查找并跳转到类或方法 ⌘ + ⇧ + O

清除编译文件夹缓存 ⌘ + ⇧ + K

在当前文件跳转到行数 ⌘ + L

清空日志 ⌘ + K

在当前文件跳转到行数 ⌘ + L

定位当前文件在左边项目目录中的位置 ⌘ + ⇧ + J

.h 与 .m 文件切换 ⌘ + ⌃ + ↑ (↓)

撤销 ⌘ + Z

反撤销 ⌘ + ⇧ + Z

选中的代码向左位移 ⌘ + [

选中的代码向右位移 ⌘ + ]

选中的代码向上位移 ⌘ + ⌥ + [

选中的代码向下位移 ⌘ + ⌥ + ]

向上切换运行设备 ⌘ + ⌥ + ⌃ + [

向下切换运行设备 ⌘ + ⌥ + ⌃ + ]

选中的代码自动缩进 ⌃ + I

跳转到类, 方法或变量的定义代码处 ⌘ + ⌃ + J

折叠代码块 ⌘ + ⌥ + ←

展开代码块 ⌘ + ⌥ + →

返回到上个页面 ⌘ + ⌃ + ←

前进到下个页面 ⌘ + ⌃ + →

新建文件 ⌘ + N

新建工程 ⌘ + ⇧ + N

打开/关闭所有断点 ⌘ + Y

暂停时跳过此断点 ⌘ + ⌃ + Y

单步调试 F6

光标跳到文件首部 ⌘ + ↑

光标跳到文件尾部 ⌘ + ↓

光标跳到行首 ⌘ + ←

光标跳到行尾 ⌘ + →

光标跳到单词首部 ⌥ + ←

光标跳到单词尾部 ⌥ + →

左侧navigator跳转 ⌘ + 1~9

navigator

显示/隐藏左侧面板 ⌘ + 0

显示/隐藏右侧面板 ⌘ + ⌥ + 0

显示/隐藏下边控制台 ⌘ + ⇧ + Y

panel

当前类里查找方法或变量 ⌃ + 6 并可直接输入查找关键字, 回车跳转

panel

打开/隐藏Toolbar ⌘ + ⌥ + T

panel

__原创文章, 转载请注明出处

Parsing JSON in Swift 4 with Decodable

Trying to write a post in English. 👻

Now Apple support parsing JSON directly in Swift. I write an example to show how to do.
Download Demo

Before Swift 4

1
2
3
4
5
6
7
// Fake datas
let aBand: [String: Any] = [
"id": 23,
"name": "Nirvana"
]
// Assume we got a response data from an api
let data = try JSONSerialization.data(withJSONObject: aBand, options: .prettyPrinted)

Object Code:

1
2
3
4
5
6
7
8
9
10
struct Band {
let id: Int
let name: String?
// we need a initializer to deal with the JSON in swift 2/3 or ObjC
init(json: [String: Any]) {
id = json["id"] as? Int ?? -1
name = json["name"] as? String ?? ""
}
}

Here is what we do in Swift 2/3 or ObjC:

1
2
3
4
5
6
7
8
9
do {
guard let dict = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
throw NSError()
}
let band = Band(json: dict)
}
catch let error {
print(error)
}

Swift 4

Dictionary structure

1
2
3
4
let aBand: [String: Any] = [
"id": 23,
"name": "Nirvana"
]
1
2
3
4
5
6
7
8
9
10
11
12
struct Band: Decodable {
let id: Int
let name: String
}
do {
let band = try JSONDecoder().decode(Band.self, from: data)
print(band)
}
catch let error {
print(error)
}

Result: Band(id: 23, name: "Nirvana")

Array structure

1
2
3
4
5
6
7
8
9
10
let bands: [[String: Any]] = [
[
"id": 23,
"name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles"
]
]

Just change Band.self to [Band].self

1
let band = try JSONDecoder().decode([Band].self, from: data)

Result: [JsonDecode.Band(id: 23, name: "Nirvana"), JsonDecode.Band(id: 22, name: "The Beatles")]

Nested structure

1
2
3
4
5
6
7
8
9
10
11
12
13
let type: [String: Any] = [
"name": "Rock",
"examples": [
[
"id": 23,
"name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles"
]
]
]

Create a new type:

1
2
3
4
struct MusicType: Decodable {
let name: String
let examples: [Band]
}
1
2
3
4
5
6
7
8
9
10
11
12
struct Band: Decodable {
let id: Int
let name: String
}
do {
let band = try JSONDecoder().decode(MusicType.self, from: data)
print(band)
}
catch let error {
print(error)
}

Result: MusicType(name: "Rock", examples: [JsonDecode.Band(id: 23, name: "Nirvana"), JsonDecode.Band(id: 22, name: "The Beatles")])

Optional field

What if we got a JSON like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let type: [String: Any] = [
"name": "Rock",
"examples": [
[
"id": 23,
// "name": "Nirvana"
],
[
"id": 22,
"name": "The Beatles",
"country": "UK"
]
]
]

OK, obviously the decoder will ignore the “country” key, but can not find the key “name”, we will get a error:

1
2
3
4
5
keyNotFound(CodingKeys(stringValue: "name", intValue: nil),
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "examples", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)],
debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").",
underlyingError: nil))

All we need to do is to make the name property to Optional:

1
2
3
4
struct Band: Decodable {
let id: Int
let name: String?
}

Result: MusicType(name: "Rock", examples: [JsonDecode.Band(id: 23, name: nil), JsonDecode.Band(id: 22, name: Optional("The Beatles"))])

Custom keys

If the field in JSON is not what we want:

1
2
3
4
let aBand: [String: Any] = [
"id": 23,
"band_name": "Nirvana"
]

We need to define a nested enum in our type called “CodingKeys” (or use a typealias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are decoded with.

1
2
3
4
5
6
7
8
9
struct Band: Decodable {
let id: Int
let name: String
private enum CodingKeys : String, CodingKey {
case id
case name = "band_name"
}
}

Results: Band(id: 23, name: "Nirvana")

But we need to write all the other properties in the enum, what we expected is adding others automatically in compiling time.
And also we expect a many-to-one ability, like "band_name" | "bandName" => name.
Hope Apple to update.

Update on 30th March, 2018 for Swift 4.1

1
2
3
4
5
6
7
8
struct Band: Decodable {
let id: Int
let bandName: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let band = try! decoder.decode(Band.self, from: data)

Results: Band(id: 23, name: "Nirvana")

Encode

Same like Decodable, We can use Encodable, or just use Codable

1
public typealias Codable = Decodable & Encodable

__Original articles, please indicate the source if you reproduced it.

iOS中捕获exception和signal

Crash分为两种,一种是程序抛出的异常,没有被捕获造成的;另一种是signal类型的异常。针对未被捕获的异常可以使用NSSetUncaughtExceptionHandler系统方法来设置异常处理函数;对于signal类型的异常,需要使用signal系统方法给每种需要处理的signal类型的异常设置处理函数。本文Demo

核心代码

捕获exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void HandleException(NSException *exception) {
NSArray *stackArray = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
NSLog(@"%@", exceptionInfo);
}
void InstallUncaughtExceptionHandler(void) {
NSSetUncaughtExceptionHandler(&HandleException);
}

捕获Signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void SignalExceptionHandler(int signal) {
NSMutableString *crashInfo = [[NSMutableString alloc] init];
[crashInfo appendString:@"Stack:\n"];
void *callstack[128];
int i, frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
[crashInfo appendFormat:@"%s\n", strs[i]];
}
}
void InstallSignalHandler(void) {
signal(SIGHUP, SignalExceptionHandler);
signal(SIGINT, SignalExceptionHandler);
signal(SIGQUIT, SignalExceptionHandler);
signal(SIGABRT, SignalExceptionHandler);
signal(SIGILL, SignalExceptionHandler);
signal(SIGSEGV, SignalExceptionHandler);
signal(SIGFPE, SignalExceptionHandler);
signal(SIGBUS, SignalExceptionHandler);
signal(SIGPIPE, SignalExceptionHandler);
}

Signal信号类型

define SIGHUP 1 / hangup /
SIGHUP是Unix系统管理员很常用的一个信号。许多后台服务进程在接受到该信号后将会重新读取它们的配置文件。然而,该信号的实际功能是通知进程它的控制终端被断开。缺省行为是终止进程。

define SIGINT 2 / interrupt /
对于Unix使用者来说,SIGINT是另外一个常用的信号。许多shell的CTRL-C组合使得这个信号被大家所熟知。该信号的正式名字是中断信号。缺省行为是终止进程。

define SIGQUIT 3 / quit /
SIGQUIT信号被用于接收shell的CTRL-/组合。另外,它还用于告知进程退出。这是一个常用信号,用来通知应用程序从容的(译注:即在结束前执行一些退出动作)关闭。缺省行为是终止进程,并且创建一个核心转储。

define SIGILL 4 / illegal instr. (not reset when caught) /
如果正在执行的进程中包含非法指令,操作系统将向该进程发送SIGILL信号。如果你的程序使用了线程,或者pointer functions,那么可能的话可以尝试捕获该信号来协助调试。([color=Red]注意:原文这句为:“If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中间的两个use of use of,不知是原书排版的瑕疵还是我确实没有明白其意义;另外,偶经常听说functions pointer,对于pointer functions,google了一下,应该是fortran里面的东西,不管怎样,还真不知道,确切含义还请知道的兄弟斧正。[/color])缺省行为是终止进程,并且创建一个核心转储。

define SIGTRAP 5 / trace trap (not reset when caught) /
SIGTRAP这个信号是由POSIX标准定义的,用于调试目的。当被调试进程接收到该信号时,就意味着它到达了某一个调试断点。一旦这个信号被交付,被调试的进程就会停止,并且它的父进程将接到通知。缺省行为是终止进程,并且创建一个核心转储。

define SIGABRT 6 / abort() /
SIGABRT提供了一种在异常终止(abort)一个进程的同时创建一个核心转储的方法。然而如果该信号被捕获,并且信号处理句柄没有返回,那么进程不会终止。缺省行为是终止进程,并且创建一个核心转储。

define SIGFPE 8 / floating point exception /
当进程发生一个浮点错误时,SIGFPE信号被发送给该进程。对于那些处理复杂数学运算的程序,一般会建议你捕获该信号。缺省行为是终止进程,并且创建一个核心转储。

define SIGKILL 9 / kill (cannot be caught or ignored) /
SIGKILL是这些信号中最难对付的一个。正如你在它旁边的注释中看到的那样,这个信号不能被捕获或忽略。一旦该信号被交付给一个进程,那么这个进程就会终止。然而,会有一些极少数情况SIGKILL不会终止进程。这些罕见的情形在处理一个“非中断操作”(比如磁盘I/O)的时候发生。虽然这样的情形极少发生,然而一旦发生的话,会造成进程死锁。唯一结束进程的办法就只有重新启动了。缺省行为是终止进程。

define SIGBUS 10 / bus error /
如同它的名字暗示的那样,CPU检测到数据总线上的错误时将产生SIGBUS信号。当程序尝试去访问一个没有正确对齐的内存地址时就会产生该信号。缺省行为是终止进程,并且创建一个核心转储。

define SIGSEGV 11 / segmentation violation /
SIGSEGV是另一个C/C++程序员很熟悉的信号。当程序没有权利访问一个受保护的内存地址时,或者访问无效的虚拟内存地址(脏指针,dirty pointers,译注:由于没有和后备存储器中内容进行同步而造成。关于野指针,可以参见http://en.wikipedia.org/wiki/Wild_pointer 的解释。)时,会产生这个信号。缺省行为是终止进程,并且创建一个核心转储。

define SIGSYS 12 / non-existent system call invoked /
SIGSYS信号会在进程执行一个不存在的系统调用时被交付。操作系统会交付该信号,并且进程会被终止。缺省行为是终止进程,并且创建一个核心转储。

define SIGPIPE 13 / write on a pipe with no one to read it /
管道的作用就像电话一样,允许进程之间的通信。如果进程尝试对管道执行写操作,然而管道的另一边却没有回应者时,操作系统会将SIGPIPE信号交付给这个讨厌的进程(这里就是那个打算写入的进程)。缺省行为是终止进程。

define SIGALRM 14 / alarm clock /
在进程的计时器到期的时候,SIGALRM信号会被交付(delivered)给进程。这些计时器由本章后面将会提及
的setitimer和alarm调用设置。缺省行为是终止进程。

define SIGTERM 15 / software termination signal from kill /
SIGTERM信号被发送给进程,通知该进程是时候终止了,并且在终止之前做一些清理活动。SIGTERM信号是Unix的kill命令发送的缺省信号,同时也是操作系统关闭时向进程发送的缺省信号。缺省行为是终止进程。

define SIGURG 16 / urgent condition on IO channel /
在进程已打开的套接字上发生某些情况时,SIGURG将被发送给该进程。如果进程不捕获这个信号的话,那么将被丢弃。缺省行为是丢弃这个信号。

define SIGSTOP 17 / sendable stop signal not from tty /
本信号不能被捕获或忽略。一旦进程接收到SIGSTOP信号,它会立即停止(stop),直到接收到另一个SIGCONT
信号为止。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。

define SIGTSTP 18 / stop signal from tty /
SIGSTP与SIGSTOP类似,它们的区别在于SIGSTP信号可以被捕获或忽略。当shell从键盘接收到CTRL-Z的时候就会交付(deliver)这个信号给进程。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。

define SIGCONT 19 / continue a stopped process /
SIGCONT也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。这样做很有意义:因为进程大概不愿意忽略或阻塞SIGCONT信号,否则,如果进程接收到SIGSTOP或SIGSTP的时候该怎么办?缺省行为是丢弃该信号。

define SIGCHLD 20 / to parent on child stop or exit /
SIGCHLD是由Berkeley Unix引入的,并且比SRV 4 Unix上的实现有更好的接口。(如果信号是一个没有追溯能力的过程(not a retroactive process),那么BSD的SIGCHID信号实现会比较好。在system V Unix的实现中,如果进程要求捕获该信号,操作系统会检查是否存在有任何未完成的子进程(这些子进程是已经退出exit)的子进程,并且在等待调用wait的父进程收集它们的状态)。如果子进程退出的时候附带有一些终止信息(terminating information),那么信号处理句柄就会被调用。所以,仅仅要求捕获这个信号会导致信号处理句柄被调用(译注:即是上面说的“信号的追溯能力”),而这是却一种相当混乱的状况。)一旦一个进程的子进程状态发生改变,SIGCHLD信号就会被发送给该进程。就像我在前面章节提到的,父进程虽然可以fork出子进程,但没有必要等待子进程退出。一般来说这是不太好的,因为这样的话,一旦进程退出就可能会变成一个僵尸进程。可是如果父进程捕获SIGCHLD信号的话,它就可以使用wait系列调用中的某一个去收集子进程状态,或者判断发生了什么事情。当发送SIGSTOP,SIGSTP或SIGCONF信号给子进程时,SIGCHLD信号也会被发送给父进程。缺省行为是丢弃该信号。

define SIGTTIN 21 / to readers pgrp upon background tty read /
当一个后台进程尝试进行一个读操作时,SIGTTIN信号被发送给该进程。进程将会阻塞直到接收到SIGCONT信号为止。缺省行为是停止进程,直到接收到SIGCONT信号。

define SIGTTOU 22 / like TTIN if (tp->t_local&LTOSTOP) /
SIGTTOU信号与SIGTTIN很相似,不同之处在于SIGTTOU信号是由于后台进程尝试对一个设置了TOSTOP属性的tty执行写操作时才会产生。然而,如果tty没有设置这个属性,SIGTTOU就不会被发送。缺省行为是停止进程,直到接收到SIGCONT信号。

define SIGIO 23 / input/output possible signal /
如果进程在一个文件描述符上有I/O操作的话,SIGIO信号将被发送给这个进程。进程可以通过fcntl调用来设置。缺省行为是丢弃该信号。

define SIGXCPU 24 / exceeded CPU time limit /
如果一旦进程超出了它可以使用的CPU限制(CPU limit),SIGXCPU信号就被发送给它。这个限制可以使用随后讨论的setrlimit设置。缺省行为是终止进程。

define SIGXFSZ 25 / exceeded file size limit /
如果一旦进程超出了它可以使用的文件大小限制,SIGXFSZ信号就被发送给它。稍后我们会继续讨论这个信号。缺省行为是终止进程。

define SIGVTALRM 26 / virtual time alarm /
如果一旦进程超过了它设定的虚拟计时器计数时,SIGVTALRM信号就被发送给它。缺省行为是终止进程。

define SIGPROF 27 / profiling time alarm /
当设置了计时器时,SIGPROF是另一个将会发送给进程的信号。缺省行为是终止进程。

define SIGWINCH 28 / window size changes /
当进程调整了终端的行或列时(比如增大你的xterm的尺寸),SIGWINCH信号被发送给该进程。缺省行为是丢弃该信号。

define SIGUSR1 29 / user defined signal 1 /
define SIGUSR2 30 / user defined signal 2 /
SIGUSR1和SIGUSR2这两个信号被设计为用户指定。它们可以被设定来完成你的任何需要。换句话说,操作系统没有任何行为与这两个信号关联。缺省行为是终止进程。(译注:按原文的意思翻译出来似乎这两句话有点矛盾。)

Objective-C 中的消息发送和转发

Objective-C 的方法调用被苹果成为”发消息”. objc的消息机制是由运行时实现、非常灵活动态。这篇文章简单记录一下objc运行时对于消息发送和转发的实现. 本文Demo

编译器转换

1
[cat mew];

意为cat对象调用mew方法, 也就是向cat对象发送mew消息, 实际上编译器会将这行代码转换为

1
objc_msgSend(cat, @selector(mew));

验证如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <objc/message.h>
@interface Cat : NSObject
@property (nonatomic, copy) NSString *name;
- (void)mew;
@end
@implementation Cat
- (void)mew {
NSLog(@"喵~");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = Cat.new;
[cat mew];
objc_msgSend(cat, @selector(mew));
}
return 0;
}

结果是输出两次:

1
2
喵~
喵~

当然我们也可以执行 clang -rewrite-objc main.m 将oc转换为C++代码来查看.

objc_msgSend

文档中这样写:

1
id objc_msgSend(id self, SEL _cmd, ...)

将一个消息发送给一个对象,并且返回一个值。
其中,self是消息的接受者,_cmd是selector, …是可变参数列表。

为了了解objc_msgSend方法做了什么,这里需要查看一下objc runtime的源码
首先 runtime定义了如下的数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 类具体的信息
class_rw_t *data() {
return bits.data();
}
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
...
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // 对象占用空间
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; // 类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
typedef struct objc_selector *SEL;
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);

id指代objc中的对象,每个对象的在内存的结构并不是确定的,但其首地址指向的肯定是isa。通过isa指针,运行时就能获取到objc_class。

objc_class表示对象的Class,它的结构是确定的,由编译器生成。

SEL表示选择器,这是一个不透明结构体。但是实际上,通常可以把它理解为一个字符串。例如printf(“%s”,@selector(isEqual:))会打印出”isEqual:”。运行时维护着一张SEL的表,将相同字符串的方法名映射到唯一一个SEL。 通过sel_registerName(char *name)方法,可以查找到这张表中方法名对应的SEL。苹果提供了一个语法糖@selector用来方便地调用该函数。

IMP是一个函数指针。objc中的方法最终会被转换成纯C的函数,IMP就是为了表示这些函数的地址。

那么objc_msgSend究竟做了什么呢? 这个方法是由汇编实现得, 用伪代码大概可以表示为:

1
2
3
4
5
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); //调用这个函数,伪代码...
}

另外在objc-msg-arm64.s 中找到如下关键代码

1
2
3
.macro MethodTableLookup
...
bl __class_lookupMethodAndLoadCache3

以及objc-runtime-new.mm :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
}

[cat mew];为例, 总结一下就是:

  1. 通过cat的isa指针找到它的class, 也就是Cat类
  2. 在Cat的cache列表中查找mew
  3. 在Cat的method列表中查找mew
  4. 在父类的cache和method列表中查找mew
  5. 在2-4的过程中, 一旦找到就将该方法添加到缓存列表并执行该方法
  6. 没有找到任何的方法实现, Try method resolver once, 也就是我们说的resolveInstanceMethod方法
  7. resolveInstanceMethod没实现, 就进入转发机制

另需注意: 如果是类方法的调用, 则是去Cat的元类中查找, 因为类方法都是保存在meta class中, 依次向父元类查找, 注意, 到NSObject meta class, 它的父类是NSObject本类, 如果这个时候有一个同名的实例方法, 也可以调用. 例:

1
2
3
4
5
6
7
8
9
10
11
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: - [NSObject (sark) foo]");
}
@end
[NSObject foo]; // 打印 IMP: - [NSObject (sark) foo]

消息转发

首先我们先来验证一下, 调用一个不存在的方法, 在运行时发了哪些消息

1
2
Cat *cat = Cat.new;
[cat performSelector:@selector(lalala)];

具体方法如下:

  1. 断点暂停后执行call (void)instrumentObjcMessageSends(YES)

  2. 然后过掉断点, 程序崩溃
1
2
3
4
5
6
7
8
9
10
11
12
13
2018-03-01 17:37:05.671634+0800 msg_send[18226:862391] -[Cat lalala]: unrecognized selector sent to instance 0x10054d9e0
2018-03-01 17:37:05.676829+0800 msg_send[18226:862391] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Cat lalala]: unrecognized selector sent to instance 0x10054d9e0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff4eabc54b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fff7658bc76 objc_exception_throw + 48
2 CoreFoundation 0x00007fff4eb55024 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x00007fff4ea32a90 ___forwarding___ + 1456
4 CoreFoundation 0x00007fff4ea32458 _CF_forwarding_prep_0 + 120
5 msg_send 0x0000000100001c76 main + 86
6 libdyld.dylib 0x00007fff771a5015 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

3.在Terminal中输入:

1
open /private/tmp


4.打开查看

1
2
3
4
5
6
7
8
9
10
11
- Cat NSObject performSelector:
+ Cat NSObject resolveInstanceMethod:
+ Cat NSObject resolveInstanceMethod:
- Cat NSObject forwardingTargetForSelector:
- Cat NSObject forwardingTargetForSelector:
- Cat NSObject methodSignatureForSelector:
- Cat NSObject methodSignatureForSelector:
- Cat NSObject class
- Cat NSObject doesNotRecognizeSelector:
- Cat NSObject doesNotRecognizeSelector:
- Cat NSObject class

结合 NSObject官方文档, 转发机制可如图表示:

1.调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。

2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。

3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。

4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。

上面这4个方法均是模板方法,开发者可以override,由runtime来调用。最常见的实现消息转发,就是重写方法3和4,吞掉一个消息或者代理给其他对象都是没问题的。

示例: 我们为Dog类添加name属性和实例, 但通过@dynamic使其不自动生成setter和getter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Dog {
NSString *_name;
}
@dynamic name;
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [Dog new];
dog.name = @"Kiki";
NSLog(@"%@", dog.name);
}
return 0;
}

此时dog对象没有setName: 方法, 会尝试解决, 我们来实现resolveInstanceMethod 方法, 并为name添加setter 和 getter (他们的内部实现可以用指针指向, 也可以用运行时api)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@implementation Dog {
NSString *_name;
}
@dynamic name;
void mySetName(id self, SEL _cmd, NSString *newValue) {
// Ivar ivar = class_getInstanceVariable(Dog.class, "_name");
// object_setIvar(self, ivar, [newValue copy]);
if (((Dog *)self)->_name != newValue) {
((Dog *)self)->_name = [newValue copy];
}
}
NSString * myGetName(id self, SEL _cmd) {
// Ivar ivar = class_getInstanceVariable(Dog.class, "_name");
// return object_getIvar(self, ivar);
return ((Dog *)self)->_name;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"%s", __func__);
if ([NSStringFromSelector(sel) isEqualToString:@"setName:"]) {
class_addMethod(self, sel, (IMP)mySetName, "v@:@");
}
else {
class_addMethod(self, sel, (IMP)myGetName, "@@:");
}
return YES;
}
@end

如果不实现上述方法, 也可以转发给别的对象:

1
2
3
4
5
6
7
8
9
10
@implementation Dog {
NSString *_name;
}
@dynamic name;
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
return Cat.new;
}
@end

该消息转发给Cat类的一个对象, 去调用Cat的setName:方法, 当然这没什么实际的意义.

第三次补救:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@implementation Dog {
NSString *_name;
}
@dynamic name;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"%s", __func__);
NSString *selStr = NSStringFromSelector(aSelector);
if([selStr isEqualToString:@"name"]) {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:"];
return sig;
}
else {
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return sig;
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"%s", __func__);
SEL sel = [anInvocation selector];
Cat *cat = [[Cat alloc] init];
if([cat respondsToSelector:sel]) {
[anInvocation invokeWithTarget:cat];
}
else {
[self doesNotRecognizeSelector:sel];
}
}
@end

首先生成方法签名, 然后转发调用, 如果没有实现forward, 那直接调用doesNotRecognizeSelector, 抛出exception, 也可以不使用doesNotRecognizeSelector, 这样就吞没了这个消息.

__原创文章, 转载请注明出处

atexit - python程序结束时的回调

用python写一些api测试, 需要在程序结束前记录时间, 尝试了各种中文的英文的关键词, 终于搜索到atexit这个模块.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import atexit
def exit_function():
print("exiting")
def exit_function2(name, age):
print("exiting2: " + name + " " + str(age))
@atexit.register
def exit_function3():
print("exiting3")
if __name__ == '__main__':
atexit.register(exit_function)
atexit.register(exit_function2, 'Phoenix', 22)

可注册多个回调函数, 也可以传入参数, 也可以使用装饰器来修饰需要回调的函数, 执行顺序是先进后出, 代码由上至下, 所以注册顺序是exit_function3, exit_function, exit_function2, 所以执行顺序反过来:

1
2
3
exiting2: 22Phoenix
exiting
exiting3

注意: 如果程序是非正常crash,或通过os._exit()退出,注册的回调函数将不会被调用.

Python Fabric 自动化部署

最近公司在做在线抓娃娃, 领导让我写个运维脚本一键部署代码到所有娃娃机, 要求时间是一天之内搞定, 很好, 感谢您的信任, 让一个python零基础的人做这个, 我的内心是崩溃的….只能先找个python基础的帖子过一遍, 然后开始愣写. 现在为什么写这篇文章分享呢, 因为我真的一天之内写出来了, 看来人真的是逼出来的.

了解到fabric框架可以实现多台远程服务器部署, 而我们娃娃机用的是树莓派安装的Ubuntu, 同样都是linux所以适用

安装fabric

1
$ pip install fabric

创建fabric脚本

新建名为fabfile.py的文件, 添加代码:

1
2
def hello():
print("hello world")
1
2
$ fab hello
hello world

fabfile.py 是fabric默认识别的文件名, 也可以使用别的文件名, 但使用时加参数, 如:

1
2
$ fab -f test.py hello
hello world

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: utf-8 -*-
from fabric.api import *
from fabric.contrib.files import append
env.hosts = ['192.168.0.1', '192.168.0.2:20001'] # 地址列表
env.password = 'I am the password of the remote server'
env.user = 'root'
env.colorize_errors = True # 出错时以红色显示日志
'''
fabric 中lcd表示在本地服务器执行cd, local表示在本地执行命令
@runs_once 表示不管有多少个host, @runs_once修饰的方法只执行一次
此方法就是拉代码, 但要配置ssh key, 否则手动输git账户密码怎么能叫一键部署呢
'''
@runs_once
def prepare():
with lcd('/home/yunai/wawaji/device/'):
local('git pull')
'''
@parallel 修饰的方法就是在每台机器上都执行的方法
pool_size 表示最大并发数量
'''
@parallel(pool_size=10)
def update():
# 输出等级设置,隐藏指定的类型
with settings(
hide('warnings', 'running', 'stdout', 'stderr'),
warn_only=True
):
# 实际业务, 我们是以某个文件的某行内容来作为是否更新过的标准, 这个就根据实际需求来制定就好
if sudo('grep internal /home/yunai/wawaji/control/control.py'):
# env.host_string 表示当前正在更新的机器的host
print env.host_string + ' had been updated before, canceled.'
else:
print env.host_string + ' updating file...'
# 以sudo更新一个文件
put(local_path='/home/yunai/wawaji/device/control/control.py', remote_path='/home/yunai/wawaji/control/', use_sudo=True, mode=755)
# 以sudo给文件追加内容, 相当于:
# sudo('echo "NTP=192.168.0.3 192.168.0.2" >> /etc/systemd/timesyncd.conf')
# 但fabric的append会自动识别文件末是否已有该内容, 如果有, 则不再追加
append('/etc/systemd/timesyncd.conf', 'NTP=192.168.0.3 192.168.0.2', use_sudo=True)
print env.host_string + ' update completed'
'''
在外部设置ip的方法
也可以不使用该方法, 在内部直接写好所有ip
'''
def set_ips(*ips):
[env.hosts.append(p) for p in ips if p not in env.hosts]

所以我们可以这样来调用

1
$ fab set_ips:'192.168.0.3','192.168.0.22' prepare update

从数据库获取ip

初期领导给我的ip列表都是截图, 我的内心那是相当崩溃的, 难道手打出来吗, 当然不是, 机智的我找了个图片识别网站先转成文本, 再用正则匹配出所有ip. 后期步入正轨用数据库就好了, 下面来说python怎么使用pymysql模块来操作数据库

安装pymysql

1
$ pip install pymysql

操作数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pymysql
def all_ip():
connection = pymysql.connect(
host = "127.0.0.1",
user = "root",
password = "I am the password of mysql",
database = "doll", # 数据库名
charset = 'utf8'
# 我们可以在此处来配置返回类型, 如下将按字典返回, 不定义则返回tuple
# cursorclass = pymysql.cursors.DictCursor
# 文档链接: http://mysql-python.sourceforge.net/MySQLdb-1.2.2/public/MySQLdb.cursors-module.html
)
cursor = connection.cursor()
result = cursor.execute("select ipaddress from device;") # 查表
result = cursor.fetchall()
cursor.close()
connection.close()
# 此时result结果应为(('192.168.0.1',), ('192.168.0.33',),)
new_array = []
for t in map(lambda x: x, result):
for ip in t:
new_array.append(ip)
# 将ip数组转换为方法参数格式
ipset = set(new_array)
# 内部设置ip列表
set_ips(*ipset)

最后我们就可以直接这样来调用了

1
$ fab all_ip update

附上Fabric文档链接 http://docs.fabfile.org/en/1.14/ 需要什么查文档就好了!!

Hexo 使用教程

太久没写博客都忘了咋用了, 还得现去查, 还是自己来记录下一下吧, 毕竟再简单的东西也禁不住忘啊…

进入目录…

1
cd ~/asura19.github.io

新建文章

1
hexo new "一个标题"

打开文章

1
在路径 ~/asura19.github.io/source/_posts/ 下打开相应文章

用Markdown写文章

“blablabla…”

生成静态文件

1
hexo generate (可简写为 hexo g)

启动本地服务器查看

1
hexo server (可简写为 hexo s)

部署到网站

1
hexo deploy (可简写为 hexo d)

重新填写自定义域名

如果在github配置了自定义域名, 那么hexo deploy之后不知为何会将域名消掉, 所以要在项目Setting-Custom domain重新填写域名

Objective-C runtime 常用api及示例

列举一些常用的运行时api. 本文Demo

Demo类代码

Cat.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@protocol Cute
@optional
- (void)isCute;
@end
@protocol Movable
@optional
- (void)run;
@end
@interface Cat : NSObject<Movable>
@property (nonatomic, copy) NSString *name;
- (void)mew;
@end

Cat.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@implementation Cat {
NSColor *_color;
}
+ (void)mews {
NSLog(@"+++++喵+++++");
}
- (instancetype)init {
self = [super init];
if (self) {
_color = [NSColor blackColor];
}
return self;
}
- (void)mew {
NSLog(@"-----喵-----");
}
- (void)run {
NSLog(@"~~run~~");
}
- (NSArray *)method0:(NSArray *)agu0
agu1:(CGFloat)agu1
agu2:(NSObject *)agu2
agu3:(NSString *)agu3
agu4:(CGRect)agu4 {
NSLog(@"test method");
return @[];
}
- (void)method0 {
NSLog(@">>>>>>>>0:%s", __func__);
}
- (void)method1 {
NSLog(@">>>>>>>>1:%s", __func__);
}
- (void)method2 {
NSLog(@">>>>>>>>2:%s", __func__);
}
+ (void)catClassMethod {
NSLog(@"~~cat class method~~");
}
@end

Cat+YYAdd.h

1
2
3
4
@interface Cat (YYAdd)
@property (nonatomic, assign) float weight;
- (void)jump;
@end

Cat+YYAdd.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation Cat (YYAdd)
- (void)setWeight:(float)weight {
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_ASSIGN);
}
- (float)weight {
return [objc_getAssociatedObject(self, _cmd) floatValue];
}
- (void)jump {
NSLog(@"~~Cat jump~~");
}
@end

Runtime Demo

class部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// 获取类名
const char *className = class_getName(Cat.class);
NSLog(@"%@", [NSString stringWithUTF8String:className]);
// 结果: Cat
// 获取父类
Class superClass = class_getSuperclass(Cat.class);
NSLog(@"%@", superClass);
// 结果: NSObject
// 该函数的作用是获取类的实例所占用内存的大小
size_t instanceSizeOfClass = class_getInstanceSize(Cat.class);
NSLog(@"%zu", instanceSizeOfClass);
// 结果: 24
// 获取实例变量
Ivar ivar = class_getInstanceVariable(Cat.class, "_color");
NSLog(@"%@", [NSString stringWithUTF8String:ivar_getName(ivar)]);
// 结果: _color
// 获取属性
objc_property_t prop = class_getProperty(Cat.class, "name");
NSLog(@"%@", [NSString stringWithUTF8String:property_getName(prop)]);
// 结果: name
// 获取实例方法的实现, 并执行
IMP mewImp = class_getMethodImplementation(Cat.class, @selector(mew));
mewImp();
// 结果: -----喵-----
// 获取类的所有实例
unsigned int ivarListCount = 0;
Ivar *ivars = class_copyIvarList(Cat.class, &ivarListCount);
for (NSInteger i = 0; i < ivarListCount; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSLog(@"ivarList: %@", [NSString stringWithUTF8String:name]);
}
free(ivars);
// 结果:
ivarList: _color
ivarList: _name
// 获取类的所有属性
unsigned int propertyListCount = 0;
objc_property_t *props = class_copyPropertyList(Cat.class, &propertyListCount);
for (NSInteger i = 0; i < propertyListCount; i++) {
objc_property_t property = props[i];
const char *name = property_getName(property);
NSLog(@"propertyList: %@", [NSString stringWithUTF8String:name]);
}
free(props);
// 结果:
propertyList: weight
propertyList: name
// 由此可见, 运行时添加的关联属性, 没有生成实例
// 获取类的所有实例方法
unsigned int methodListCount = 0;
Method *methods = class_copyMethodList(Cat.class, &methodListCount);
for (NSInteger i = 0; i < methodListCount; i++) {
Method method = methods[i];
SEL name = method_getName(method);
NSLog(@"methodList: %@", NSStringFromSelector(name));
}
free(methods);
// 结果:
methodList: mew
methodList: method0:agu1:agu2:agu3:agu4:
methodList: method0
methodList: method1
methodList: method2
methodList: init
methodList: .cxx_destruct
methodList: name
methodList: setName:
methodList: run
methodList: jump
methodList: setWeight:
methodList: weight
// 获取类所遵守的协议
unsigned int protocolListCount = 0;
Protocol * __unsafe_unretained *protocols = class_copyProtocolList(Cat.class, &protocolListCount);
for (NSInteger i = 0; i < protocolListCount; i++) {
Protocol *protocal = protocols[i];
const char *name = protocol_getName(protocal);
NSLog(@"protocolList: %@", [NSString stringWithUTF8String:name]);
}
free(protocols);
// 结果: protocolList: Movable
// 获取类的某个类方法
Method classMethod = class_getClassMethod(Cat.class, NSSelectorFromString(@"catClassMethod"));
SEL classMethodName = method_getName(classMethod);
NSLog(@"clasMethod: %@", NSStringFromSelector(classMethodName));
IMP classMethodNameIMP = method_getImplementation(classMethod);
classMethodNameIMP();
// 结果: clasMethod: catClassMethod
// 获取类的元类
Class metaClass = objc_getMetaClass("Cat");
NSLog(@"meta class: %@", metaClass);
// 结果: meta class: Cat
// 从元类中获取所有类方法
unsigned int metaMethodListCount = 0;
Method *metaMethods = class_copyMethodList(metaClass, &metaMethodListCount);
for (NSInteger i = 0; i < metaMethodListCount; i++) {
Method method = metaMethods[i];
SEL name = method_getName(method);
NSLog(@"metaMethodList: %@", NSStringFromSelector(name));
}
free(metaMethods);
// 结果:
metaMethodList: mews
metaMethodList: catClassMethod
// 证明类方法的确存放在元类中

其他api

// 获取isa指向的Class
Class object_getClass(id obj)

// 设置isa指向的Class
Class object_setClass(id obj, Class cls)

// 判断一个OC对象是否为Class
BOOL object_isClass(id obj)

// 判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)

object部分

1
2
3
4
5
6
7
8
9
10
11
12
Cat *cat = Cat.new;
cat.name = @"小麻烦";
cat.weight = 5.0;
NSLog(@"cat weight: %@", @(cat.weight)); // cat weight: 5
objc_removeAssociatedObjects(cat);
NSLog(@"cat weight after remove: %@", @(cat.weight)); // cat weight after remove: 0
Ivar ivar = class_getInstanceVariable(Cat.class, "_color");
NSLog(@"internal instance old value: %@", object_getIvar(cat, ivar)); // internal instance old value: Generic Gray Gamma 2.2 Profile colorspace 0 1
object_setIvar(cat, ivar, [NSColor orangeColor]);
NSLog(@"internal instance new value: %@", object_getIvar(cat, ivar)); // internal instance new value: sRGB IEC61966-2.1 colorspace 1 0.5 0 1

动态创建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// C语言实现得getter
NSString *personNameGetter(id classInstance, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([classInstance class], "_name");
return object_getIvar(classInstance, ivar);
}
// C语言实现得setter
void personNameSetter(id classInstance, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([classInstance class], "_name");
id oldName = object_getIvar(classInstance, ivar);
if (oldName != newName) object_setIvar(classInstance, ivar, [newName copy]);
}
//下面对应的编码值可以在官方文档里面找到
//编码值 含意
//c 代表char类型
//i 代表int类型
//s 代表short类型
//l 代表long类型,在64位处理器上也是按照32位处理
//q 代表long long类型
//C 代表unsigned char类型
//I 代表unsigned int类型
//S 代表unsigned short类型
//L 代表unsigned long类型
//Q 代表unsigned long long类型
//f 代表float类型
//d 代表double类型
//B 代表C++中的bool或者C99中的_Bool
//v 代表void类型
//* 代表char *类型
//@ 代表对象类型
//# 代表类对象 (Class)
//: 代表方法selector (SEL)
//[array type] 代表array
//{name=type…} 代表结构体
//(name=type…) 代表union
//bnum A bit field of num bits
//^type A pointer to type
//? An unknown type (among other things, this code is used for function pointers)
Class PersonClass = objc_allocateClassPair(NSObject.class, "Person", 0);
// 添加属性必须在objc_allocateClassPair和objc_registerClassPair之间
BOOL result = class_addIvar(PersonClass,
"_gender",
sizeof(NSString *),
log2(sizeof(NSString *)),
"@");
BOOL result2 = class_addIvar(PersonClass,
"_name",
sizeof(NSString *),
log2(sizeof(NSString *)),
"@");
// 也可以将 "@" 用@encode(NSString)表示
if (result && result2) {
objc_registerClassPair(PersonClass);
}
else {
objc_disposeClassPair(PersonClass);
}
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
// 结合文档, 添加属性需以T开头, V结尾
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = {"C", ""};
objc_property_attribute_t nonatomic = {"N", ""};
objc_property_attribute_t backingIvar = {"V", "_name"};
objc_property_attribute_t attrs[] = {type, ownership, nonatomic, backingIvar};
class_addProperty(PersonClass, "name", attrs, 4);
SEL getter = NSSelectorFromString(@"name");
SEL setter = NSSelectorFromString(@"setName:");
// 添加方法
class_addMethod(PersonClass, getter, (IMP)personNameGetter, "@@:"); // or use method_getTypeEncoding for last argument
class_addMethod(PersonClass, setter, (IMP)personNameSetter, "v@:@");
// 以下为测试该类
Ivar ivar = class_getInstanceVariable(PersonClass, "_gender");
NSLog(@"Person ivar:%@",[NSString stringWithUTF8String:ivar_getName(ivar)]);
// 结果: Person ivar:_gender
objc_property_t prop = class_getProperty(PersonClass, "name");
NSLog(@"Person property:%@",[NSString stringWithUTF8String:property_getName(prop)]);
// 结果: Person property:name
Method method = class_getInstanceMethod(PersonClass, NSSelectorFromString(@"setName:"));
NSLog(@"%@", NSStringFromSelector(method_getName(method)));
// 结果: setName:
id person = [PersonClass new];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[person performSelector:setter withObject:@"Phoenix"];
NSLog(@"person name get:%@", [person performSelector:getter withObject:nil]);
#pragma clang diagnostic pop
// 结果: person name get:Phoenix

方法部分

1
2
3
4
5
6
7
8
9
10
void newCatMethod() {
NSLog(@"~~汪~~");
}
void replaceMethod() {
class_replaceMethod(Cat.class, @selector(mew), (IMP)newCatMethod, NULL);
IMP mew = class_getMethodImplementation(Cat.class, @selector(mew));
mew();
// 结果: ~~汪~~
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
Method method = class_getInstanceMethod(Cat.class, @selector(method0:agu1:agu2:agu3:agu4:));
#pragma clang diagnostic pop
SEL methodName = method_getName(method);
NSLog(@"%@", NSStringFromSelector(methodName));
// 结果: method0:agu1:agu2:agu3:agu4:
IMP methodIMP = method_getImplementation(method);
#pragma unused(methodIMP)
const char *attrs = method_getTypeEncoding(method);
NSLog(@"%@", [NSString stringWithUTF8String:attrs]);
// 结果: @80@0:8@16d24@32@40{CGRect={CGPoint=dd}{CGSize=dd}}48
unsigned int count = method_getNumberOfArguments(method);
NSLog(@"%u", count);
// 结果: 7 因为还有self 和 _cmd
for (unsigned int i =0 ; i < count; i++) {
char result[1024] = {};
method_getArgumentType(method, i, result, 1024);
NSLog(@"类型是 %s", result);
}
char returnType[1024] = {};
method_getReturnType(method, returnType, 1024);
NSLog(@"return type:%s",returnType);
struct objc_method_description result7 = *method_getDescription(method);
NSLog(@"description ~%@ ~%@",NSStringFromSelector(result7.name),[NSString stringWithUTF8String:result7.types]);

其他API

char method_copyReturnType(Method m)
char
method_copyArgumentType(Method m, unsigned int index)

// 选择器相关
const char sel_getName(SEL sel)
SEL sel_registerName(const char
str)

// 用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

交换方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
Method result0 = class_getInstanceMethod(Cat.class, @selector(method0));
Method result1 = class_getInstanceMethod(Cat.class, @selector(method1));
Method result2 = class_getInstanceMethod(Cat.class, @selector(method2));
method_setImplementation(result0, method_getImplementation(result1));
method_getImplementation(result0)();
method_exchangeImplementations(result0, result2);
method_getImplementation(result0)();
method_getImplementation(result2)();
#pragma clang diagnostic pop
// 结果:
>>>>>>>>1:-[Cat method1]
>>>>>>>>2:-[Cat method2]
>>>>>>>>1:-[Cat method1]

新浪微博OnePassword SSO登录

发现VVebo的SSO登录在Webview使用了1Password, 觉得可以用运行时来实现.点击查看效果图

实现思路:

  1. 以苹果刚公开的工具UIDebuggingInformationOverlay查看微博SDK中的controller和view
  2. 以运行时method swizzling 替换系统viewWillAppear方法, 拦截到微博的控制器后, 在相应位置添加1Password按钮
  3. 1Password回调后以Safari调试此Webview,查看h5的elementID, 将账户密码添加进去, 自动登录

关于微博SDK, 登录接口就不详述了

就是要注意微博SDK是在iOS没有安装微博客户端的时候才会调用Webview登录

UIDebuggingInformationOverlay的使用

UIDebuggingInformationOverlay是苹果刚公开的UI调试工具
使用如下:

1
2
3
4
let overlayClass = NSClassFromString("UIDebuggingInformationOverlay") as? UIWindow.Type
_ = overlayClass?.perform(NSSelectorFromString("prepareDebuggingOverlay"))
let overlay = overlayClass?.perform(NSSelectorFromString("overlay")).takeUnretainedValue() as? UIWindow
_ = overlay?.perform(NSSelectorFromString("toggleVisibility"))

然后就可以在手机上看到对应的controller和view了:


是不是很神奇!
OK, 现在我们很方便的拿到了控制器和view的名字”WBSDKAuthorizeWebViewController” “WBSDKWebView”, 当然也有其他方式, 不多说.

swift中的method swizzling

swift3中dispatch_once_t 被取消了, 因此用全局常量来实现只执行一次swizzling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public let controllerMethodSwizzling: (UIViewController.Type) -> () = { viewController in
let originalSelector = #selector(viewController.viewWillAppear(_:))
let swizzledSelector = #selector(viewController.proj_viewWillAppear(animated:))
let originalMethod = class_getInstanceMethod(viewController, originalSelector)
let swizzledMethod = class_getInstanceMethod(viewController, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
extension UIViewController {
open override class func initialize() {
guard self === UIViewController.self else { return }
controllerMethodSwizzling(self)
}
func proj_viewWillAppear(animated: Bool) {
self.proj_viewWillAppear(animated: animated)
}
}

但是! 在initialize方法处报了个警告, xcode跟我讲这方法swift不一定调, 而且以后彻底不让在swift里用了.好吧, 那我在applicationDidFinishLaunchingWithOptions调总可以了吧.

1
2
3
4
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
controllerMethodSwizzling(UIViewController.self)
return true
}

调用OnePassword

现在我们拦截到了微博控制器的viewWillAppear方法了, 接下来就是一些基本操作了.
1Password的iOS代码地址https://github.com/agilebits/onepassword-app-extension

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func proj_viewWillAppear(animated: Bool) {
self.proj_viewWillAppear(animated: animated)
let viewControllerName = NSStringFromClass(type(of: self))
if viewControllerName == "WBSDKAuthorizeWebViewController" {
let hasOnePassword = UIApplication.shared.canOpenURL(URL(string: "org-appextension-feature-password-management://")!)
guard hasOnePassword else {
return
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "onepassword"),
landscapeImagePhone: nil,
style: .done,
target: self,
action: #selector(onePassword(_:)))
}
}
func onePassword(_ sender: AnyObject) {
for view in self.view.subviews {
if NSStringFromClass(type(of: view)) == "WBSDKWebView" {
for subview in view.subviews {
let viewClassName = NSStringFromClass(type(of: subview))
if viewClassName == "UIWebView" {
let webview = subview as! UIWebView
OnePasswordExtension.shared().findLogin(forURLString: "weibo.com", for: self
, sender: sender, completion: { (success, error) in
if (success != nil) {
let info = success as! Dictionary<String, Any>
let username: String = info["username"] as! String
let password: String = info["password"] as! String
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginName').value = '\(username)'")
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginPassword').value = '\(password)'")
webview.stringByEvaluatingJavaScript(from: "document.getElementById('loginAction').click()")
}
})
break
}
}
break
}
}
}

在获取了账户和密码之后, 用Safari调试Webview可获得elementID, 然后以Webview执行js代码即可

这种场景不太常见, 实现技术都不难, 主要提供一个思路.

__原创文章, 转载请注明出处