利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

给 Homebrew 设置代理

发表于 2019-02-23 | 分类于 MacOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。


可以给 Homebrew 工具设置代理,设置方法和步骤如下。

1、打开终端,进入用户根目录

1
cd ~/

2、查看当前目录

1
ls -al

查看是否有 .curl 文件,如果没有,新建一个吧

1
touch .curl

3、编辑 .curl 文件

写入下面内容,如下:

1
proxy=ip:port

把 ip 和 port 改为你的代理 ip 和端口值,如:

1
proxy=127.0.0.1:8087

保存文件即可。


扫码关注,你我就各多一个朋友~

了解 Emscripten

发表于 2019-02-15 | 分类于 C/C++ |

广告时间,见谅勿怪,看到了就点一下链接吧,感激不尽🙇‍!


最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。

点 这里 可以直接看教程。


Emscripten 是什么?

Emscripten 是一个 开源的编译器,可以将 C/C++ 的代码编译后高效运行在现代浏览器上面。Emscripten 的底层是基于 LLVM 编译器的,可以查看其开源的 emscripten llvm 和 emscripten clang。

下图是其编译 C/C++ 的代码的流程图:

官网 对 Emscripten 的定义:

Emscripten is a toolchain for compiling to asm.js and WebAssembly, built using LLVM, that lets you run C and C++ on the web at near-native speed without plugins.

截止本文发布,Emscripten 最新版本是 1.38.27.

安装条件

以下是我安装和使用 Emscripten 的条件。

  • macOS 版本 10.14
  • Git,可通过 Homebrew 安装
  • CMake,可通过 Homebrew 安装
  • Xcode 10.1
  • Python 2.7.x,Mac 系统自带
  • 稳定快速的网络环境,最重要和最关键的的是要有(neng)梯(fan)子(qiang)
  • 解决问题的态度和毅力

通过 emsdk 安装

安装 Emscripten 可以通过安装 emscripten SDK 来完成,emscripten SDK 可以简单的理解为是 Emscripten 的一套工具链。

在你自己的电脑上面任意新建一个目录,如我的 ~/dev/emscwork,打开终端,进入此目录。

1、下载 emsdk

1
git clone https://github.com/juj/emsdk.git

2、进入 emsdk 目录

1
cd emsdk

3、开始安装

1
2
3
4
5
6
7
8
9
10
11
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull

# Download and install the latest SDK tools.
./emsdk install latest

# Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file)
./emsdk activate latest

# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh

注意: 每次更新完 emsdk 后,依旧需要执行上面命令重新安装和激活。

另外一个比较常用的是 ./emsdk update-tags 这个命令,可以直接更新 emsdk 的最新 tags 版本,更新 tags 完成后,重新安装和激活最新版的 emsdk 套件。

配置 emsdk

如果你想在任意路径下都可以使用 emsdk 里面的各种工具(就是一些二进制可执行文件),需要为其设置环境变量。

编辑 ~/.bash_profile 文件,新增如下代码:

1
2
3
4
export EMSDK=~/emscwork/emsdk
export EMSCRIPTEN=$EMSDK/emscripten/1.38.27
export BINARYEN_ROOT=$EMSDK/binaryen/master_64bit_binaryen
export PATH=$EMSDK:$EMSCRIPTEN:$BINARYEN_ROOT:$PATH

执行下面命令, 使刚配置的文件生效。

1
source ~/.bash_profile

至此,安装和设置环境变量完成。

可以使用下面命令来查看 emsdk 的安装情况。

1
emcc --version
1
2
3
4
emcc (Emscripten gcc/clang-like replacement) 1.38.27 (commit ea5d631a5446632e195765d89a53ead71cd6de45)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

emcc 是一个可执行脚本,该脚本在 emsdk/emscripten/1.38.27 目录下。

1
emcc --help

上面命令可以查看更多关于 emcc 的使用方法.

编译 C/C++ 代码

这里举个实际的例子。

main.c

1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char ** argv)
{
printf("Emscripten show in browser...\n");
}

使用 emcc 编译,如下:

1
emcc main.c -s WASM=1 -o mz.html

这里要注意 WASM=1 这个选项,现在新版 SDK 默认 WASM=1 了,如果不想生成 .wasm 这个文件,需要指定 WASM=0 选项。

生成另外三个文件如下:

1
mz.html  mz.js  mz.wasm

简单介绍一下这三个文件

1、mz.wasm

二进制的 wasm 模块代码

2、mz.js

胶水代码,包含了原生 C 函数和 JavaScript/wasm 之间转换的 JS 文件

3、mz.html

用来加载、编译和实例化 wasm 代码并且将其输出在浏览器显示上的 HTML 文件

最后执行下面的命令,可以在 Safari 浏览器中显示效果

1
emrun mz.html

main.cpp

1
2
3
4
5
6
7
8
9
10
#include <iostream>

using namespace std;

int main(int argc, char ** argv)
{
cout << "Emscripten show in browser..." << endl;

return 0;
}

编译 C++ 文件(main.cpp)

1
emcc main.cpp -s WASM=1 -o mzcpp.html

同样的方式编译和运行 mzcpp.html 即可看到同样的效果。

Emscripten 应用场景

Emscripten 只是一个编译器,能将我们的高级语言编译为浏览器可以识别并运行的程序,这个看起来确实很诱人。

就目前来说,Emscripten 应用场景可以使用在安全和游戏上面。

1、安全

C/C++ 代码经过编译之后,会生成 wasm 格式 的二进制文件,这个安全级别较高,即使在浏览器中运行,破解者也不会很轻松的破解代码,这样一些在 JS 中涉及到安全的问题,可以使用 C/C++ 来写结合一些加密技术,然后用 Emscripten 编译。

2、游戏

如果能把用 C/C++ 语言写的游戏,转为可在浏览器直接运行的H5游戏,那就很美好了,用户不需要下载游戏,直接玩。现代浏览器技术的更新和发展已经让这个想法变成了现实,至少在主流的浏览器上面。

推荐大家看看这篇文章 JavaScript是如何工作的:与WebAssembly比较及其使用场景

参考文档

About Emscripten

Download and install

编译 C/C++ 为 WebAssembly

WebAssembly 概念


扫码关注,你我就各多一个朋友~

C 结构体指针初始化

发表于 2019-02-08 | 分类于 C/C++ |

在使用指针之前,务必要将其初始化。这个是我们最早学习 C 语言的时候,书上经常说的一个问题。在工作中,我们反而会经常忘记这条金科玉律。

本篇文章的所有代码都经 gcc-7 编译器编译过。关于在 macOS 中如何安装和使用 gcc,可以参考 GCC: Homebrew 安装 GCC 和 Binutils 这篇文章。

结构体成员指针的初始化

结构体成员指针的初始化,指的是初始化结构体中指针变量的成员。

我们举个例子,下面是 Animal 的结构体。

1
2
3
4
5
6
struct Animal {
char *name; //指针成员
int age;
char info[200]; //字符数组
struct Animal *nextAnimal; //指针成员
};

结构体 Animal 含有4个成员变量,其中 name、info 和 nextAnimal 是指针变量。

写一段测试代码,如下:

1
2
3
4
5
6
7
8
int main(int argc, const char *argv[])
{
struct Animal animal;

printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

return 0;
}

运行结果正常,终端输出如下:

1
animal's name: (null), age: 0, info: 

我们来验证一下 Animal *nextAnimal 在没有初始化的情况下,会不会有什么问题。

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, const char *argv[])
{
struct Animal animal;

printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

printf("animal.nextAnimal: %p\n", animal.nextAnimal);

printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);

return 0;
}

程序编译没有问题,运行报错

1
2
3
animal's name: (null), age: 0, info: 
animal.nextAnimal: 0x1127fa036
Segmentation fault: 11

修改一下代码,初始化一下 animal.nextAnimal 这个指针,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, const char *argv[])
{
struct Animal animal;

printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

printf("animal.nextAnimal: %p\n", animal.nextAnimal);

// 初始化指针变量
animal.nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));

printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);

return 0;
}

再次编译重新运行,还是报错。还需要初始化 animal.nextAnimal->name 这个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(int argc, const char *argv[])
{
struct Animal animal;

printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

printf("animal.nextAnimal: %p\n", animal.nextAnimal);

// 初始化指针变量
animal.nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));

// 初始化 name 变量
animal.nextAnimal->name = "cat";

printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);

return 0;
}

编译运行,一切正常。

1
2
3
animal's name: (null), age: 0, info: 
animal.nextAnimal: 0x10f0f1036
animal.nextAnimal->name: cat, age: 0, info:

通过上面的例子,结构体指针变量有些会给默认值,有些又不会给,所以都要初始化指针变量。修改一下代码,示例如下:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Animal {
char *name; //指针成员
int age;
char info[200]; //字符数组
struct Animal *nextAnimal; //指针成员
};

int main(int argc, const char *argv[])
{
struct Animal animal;

animal.name = "cat";
strcpy(animal.info, "This is a cat.");
printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

printf("animal.nextAnimal: %p\n", animal.nextAnimal);

// 初始化指针变量
animal.nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));

// 初始化变量
animal.nextAnimal->name = "cat";
strcpy(animal.nextAnimal->info, "This is a cat.");

printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);

return 0;
}

结构体指针的初始化

指的是初始化结构体指针变量。

1
2
3
4
5
6
7
8
int main(int argc, const char *argv[])
{
struct Animal *ptAnimal;

printf("ptAnimal's name: %s, age: %i, info: %s\n", ptAnimal->name, ptAnimal->age, ptAnimal->info);

return 0;
}

编译运行报错:

1
Segmentation fault: 11

同样的道理,需要初始化指针变量。完成后的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, const char *argv[])
{
struct Animal *ptAnimal;

// 初始化结构体指针
ptAnimal = (struct Animal *)malloc(sizeof(struct Animal));

ptAnimal->name = "dog";
strcpy(ptAnimal->info, "This is a big dog");

printf("ptAnimal's name: %s, age: %i, info: %s\n", ptAnimal->name, ptAnimal->age, ptAnimal->info);

// 初始化结构体指针的成员指针变量 nextAnimal
ptAnimal->nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));
ptAnimal->nextAnimal->name = "dog";
strcpy(ptAnimal->nextAnimal->info, "This is a big dog");

printf("ptAnimal->nextAnimal's name: %s, age: %i, info: %s\n",
ptAnimal->nextAnimal->name, ptAnimal->nextAnimal->age, ptAnimal->nextAnimal->info);

return 0;
}

完整示例

main.c

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Animal {
char *name; //指针成员
int age;
char info[200]; //字符数组
struct Animal *nextAnimal; //指针成员
};

int main(int argc, const char *argv[])
{
/// 验证结构体指针成员变量
{
struct Animal animal;

animal.name = "cat";
strcpy(animal.info, "This is a cat.");
printf("animal's name: %s, age: %i, info: %s\n", animal.name, animal.age, animal.info);

printf("animal.nextAnimal: %p\n", animal.nextAnimal);

// 初始化指针变量
animal.nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));

// 初始化变量
animal.nextAnimal->name = "cat";
strcpy(animal.nextAnimal->info, "This is a cat.");

printf("animal.nextAnimal->name: %s, age: %i, info: %s\n", animal.nextAnimal->name, animal.nextAnimal->age, animal.nextAnimal->info);
}

/// 验证结构体指针
{
struct Animal *ptAnimal;

// 初始化结构体指针
ptAnimal = (struct Animal *)malloc(sizeof(struct Animal));

ptAnimal->name = "dog";
strcpy(ptAnimal->info, "This is a big dog");

printf("ptAnimal's name: %s, age: %i, info: %s\n", ptAnimal->name, ptAnimal->age, ptAnimal->info);

// 初始化结构体指针的成员指针变量 nextAnimal
ptAnimal->nextAnimal = (struct Animal *)malloc(sizeof(struct Animal));
ptAnimal->nextAnimal->name = "dog";
strcpy(ptAnimal->nextAnimal->info, "This is a big dog");

printf("ptAnimal->nextAnimal's name: %s, age: %i, info: %s\n",
ptAnimal->nextAnimal->name, ptAnimal->nextAnimal->age, ptAnimal->nextAnimal->info);
}

return 0;
}

编译

1
gcc-7 main.c -o main

运行

1
./main

运行结果如下:

1
2
3
4
5
animal's name: cat, age: 0, info: This is a cat.
animal.nextAnimal: 0x0
animal.nextAnimal->name: cat, age: 0, info: This is a cat.
ptAnimal's name: dog, age: 0, info: This is a big dog
ptAnimal->nextAnimal's name: dog, age: 0, info: This is a big dog

小荷才露尖尖角,早有蜻蜓立上头~

致结婚8周年

发表于 2019-01-30 | 分类于 随笔 |

自己明明是个理科生,偏偏有颗文科生的心,多愁善感,心理总有道不完的情结。

很多人问我:“写博客是不是很费时间,把写博客的时间腾出来做点其他的不是更好吗?“

言下之意在说,反正你也成不了作家,干嘛浪费这个时间呢?人生有很多有乐趣的事情可以做,兴趣是最好的老师,不是吗?写博客的确是费时间,但这是个人的一个小爱好,所以,在我看来不算是浪费时间。坚持阅读和写作是我唯一没有放弃的爱好之一,因为它们能给我带来快乐,也是我忙碌之后停歇的港湾,能让我独立思考,静下心来憧憬美好的未来。

所以,爱你所爱吧!

2010年农历12月21日我们结婚了,在结婚之前我和太太认识了大概5年的时间,风风雨雨的一路走来,坎坷而又幸福,一个女人能为你坚守这么多年,并且总是能帮你,娶她两遍都不为过,哈哈!

那个时候家里条件很不好,在我的记忆深处,家里总是有还不完的债,每个学期的学杂费让父母焦头烂额,但最终还是被父母搞定了,所以我觉得他们很了不起。我坚持着自己的信念:“必须要考上大学,走出这里!”,母亲是家里最赞成我上学的,可能是被我炽热的学习热情给感染到了,这辈子不能忘记母亲为我上学奔波的日子,母爱之所以伟大是因为他为自己的孩子能倾出所有,并且不求回报!

大学时期,平时自己会找一些家教做,算是补贴一下日常的开销,寒暑假是我最盼望的日子,因为可以和太太在一起打工挣钱了,那时候挣钱只有一个目的,赚到学费!一到开学她就把自己积累的钱都给我了,简直是义薄云天,她还壮志豪情的说:“拿去花!”,我也厚颜无耻的接过来了。当时我在想,这姑娘就不怕我以后跑掉吗?!关于这个事情,后来我问过她,她说:“我相信你!”,朴实的回答让我无言以对。其实,当我看见她的第一眼,就毅然决定这辈子非她不娶了!

我们结婚后没几天,就离开了老家赶往深圳,我们手里也没有钱,穷的叮当响,记得去深圳的盘缠还是弟弟给我的。在深圳刚工作的日子里,每天中午我们还要自己做饭,粗茶淡饭的倒也觉得开心,就是在那个时候太太开始会做饭了,特别是捞面和蛋炒饭,至今还让我记忆犹新,每天晚上下班我都会去接她,在月光下她瘦小的身影显得格外别致,一起牵手回家,我们决定就这样幸福的奋斗下去!

结婚前我们彼此骂过、切磋过,多半是我的过错但我总是觉得自己是个男人,不能低头,不然真他妈没面子。现在想想自己挺可笑的,所以婚后我基本上没有和太太打骂过,女人的脾气很奇怪,一会晴天一会雨天,自己忍一忍就过去了,不要再火上浇油了,就像这句话所说的:“你虽然赢了吵架,但是你却输了感情”,退一步海阔天空!

2012年3月,儿子出生了,这个小家伙的到来让我们的生活变得更加忙碌了,我们算是步入了父母的行列了。太太算告别了所有的打工生涯,全职在家照顾孩子,我负责在外挣钱,一家三口幸福的生活在一起!太太自从有了儿子之后,儿子就像是他的全部,把孩子照顾的无微不至,做事情比以前认真了很多,开始关注一些公众号,读一些育儿书籍,学习别人是怎么养育孩子的,孩子在成长,太太也在成长。也许,女人从一名妻子的角色转换为母亲的角色的这个过程,算是一次蜕变,尤其是母亲的角色,她扮演的十分精彩!

2019年1月,我们的第二个孩子出生了,是个可爱的千金,我们之前也讨论过到底要不要二胎,我说生孩子太辛苦了,有一个孩子就够折腾的了,不要二胎也罢。太太坚决反对,说一个孩子太孤单,以后遇到事情连个商量的亲人都没有,必须要二胎,就这样我们孕育了这个小千金。无论你跟你的太太有多大的仇多大的冤,她能为你生孩子,就值得你为她付出一辈子。

今年是我们结婚8周年,7年之痒的传说没有在我们两个身上发生,我们会这样继续幸福下去,迎接下一个周年。

祝大家新年新气象,身体健康,阖家幸福,万事如意,诸事顺利!祝天下有情人终成眷属!


扫码关注,期待与你的交流~

Class、isa、元类

发表于 2019-01-29 | 分类于 iOS |

声明

本文的所涉及到的源码是 objc4 源码,截止到写本文最新的是 objc4-750 这个版本。

Class

我们在学习面向对象的学习中,接触最多的就是类,那么在OC类是由Class类型来表示的,Class是用C的数据结构来表示的。

看一下 NSObject 的声明,在头文件中,如下图所示:

1
2
3
4
5
6
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

可以看到:
1、NSObject 是实现了 <NSObject> 协议的。
2、NSObject 中有 Class 类型的 isa 成员变量,外界是无法访问的,另外 isa 指针可能在将来也会被隐藏起来(OBJC_ISA_AVAILABILITY标示了)。

继续看一下 Class 到底是什么?

在上面的文件中可以看到 Class 的定义,如下代码:

1
2
3
typedef struct objc_class *Class;

typedef struct objc_object *id;

可以看出 Class 是一个指向 objc_class 的结构体指针,Objective-C 中的类是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针。

在下面的头文件中看一下 objc_class 的定义,如下:

1
2
3
4
5
6
7
8
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

// ....
}

可以看出,objc_class 用来描述OC中的类,而 objc_object 用来描述OC中的对象,类(objc_class)其实也是一个对象(objc_object),另外 id 是代表对象的,它是指向 objc_object 的结构体指针,它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中 void * 指针类型的作用。

这里要注意,objc_class 的定义在 objc-runtime-old.h中和 objc-runtime-new.h 中的不一样。这里以 objc-runtime-new.h 为主,建议可以看看 被误解的 objc_class 这篇文章。

再来看一下 objc_object,如下图所示:

1
2
3
4
5
struct objc_object {
private:
isa_t isa;
// ...
}

objc_object 是一个结构体,里面有个私有成员变量 isa 是 isa_t 类型的。

而 isa_t 是一个 union 类型的,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

总之在OC中,类也是一个对象称之为类对象,根据凡是对象都有自己的类的原理,那么类对象的肯定存在自己的类,这个类就是元类(meta-class)。

元类

在说元类之前,先看一下下面的例子,创建一个 NSMutableDictionary 实例对象 dict,即向 NSMutableDictionary 发送 alloc 和 init 消息。

1
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];

上面代码的大概执行流程如下几个步骤:

1、先执行 [NSMutableDictionary alloc],但是 NSMutableDictionary 没有 +alloc 方法,于是再去父类NSObject 中查找该方法。

2、NSObject 响应 +alloc 方法,开始检测 NSMutableDictionary 类,并根据其所需的内存空间大小开始分配内存空间,然后把 isa 指针指向 NSMutableDictionary 类。同时,+alloc 也被加进 cache 列表里面。

3、接着,执行 -init 方法,如果 NSMutableDictionary 响应该方法,则直接将其加入 cache,如果不响应,则去父类查找。

4、在后期的操作中,如果再以 [[NSMutableDictionary alloc] init] 这种方式来创建字典对象,则会直接从 cache 中取出相应的方法,直接调用。

上面是创建一个实例对象的大致流程,接下来我们说说元类。

元类简单来说就是类对象的类。类描述的是对象,那么元类描述的就是Class类对象的类。元类定义了类的行为(类方法),在平时开发时,meta-class 基本是用不着接触的,但最好还是要知道它的存在,这样可以更好的理解OC的设计。

1
NSMutableDictionary *tDatas = [NSMutableDictionary dictionaryWithCapacity:5];

拿上面的示例来说,向 NSMutableDictionary 发送 dictionaryWithCapacity 这个消息的时候,Runtime 会在这个类的 meta-class 的方法列表中查找,通过 SEL 找到后取出方法中的 IMP 函数入口指针,并执行该方法,如果找不到就进行消息转发的流程中,最终可能会导致 Crash,消息转发的原理和机制可以参考 消息机制 这几篇文章。

元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。

1
Class object_getClass(id obj); 

object_getClass 可以获取一个对象的 class object,其源码实现如下:

1
2
3
4
5
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

举个例子吧,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSObject *obj = [NSObject new];
Class obj1 = object_getClass(obj);
Class obj2 = object_getClass([NSObject class]);
Class obj3 = objc_getMetaClass("NSObject");
Class obj4 = object_getClass(obj1);
const char *name = [NSStringFromClass(obj1) UTF8String];
NSLog(@"name: %s", name); //name: NSObject
Class obj5 = objc_getMetaClass(name);
Class obj6 = objc_getClass(name);

NSLog(@"obj : %@, ->%p: ", obj, obj);

NSLog(@"obj1: %@, ->%p: ", obj1, obj1);
NSLog(@"obj2: %@, ->%p: ", obj2, obj2);
NSLog(@"obj3: %@, ->%p: ", obj3, obj3);
NSLog(@"obj4: %@, ->%p: ", obj4, obj4);
NSLog(@"obj5: %@, ->%p: ", obj5, obj5);
NSLog(@"obj6: %@, ->%p: ", obj6, obj6);

打印结果如下:

1
2
3
4
5
6
7
obj : <NSObject: 0x600002b19d70>, ->0x600002b19d70:
obj1: NSObject, ->0x10c96bf38:
obj2: NSObject, ->0x10c96bee8:
obj3: NSObject, ->0x10c96bee8:
obj4: NSObject, ->0x10c96bee8:
obj5: NSObject, ->0x10c96bee8:
obj6: NSObject, ->0x10c96bf38:

可以看出,obj 是一个实例对象,obj1和obj6是一个 class object,其二者地址也一致,obj2、obj3、obj4 和 obj5 都获取到的是元类。

通过类对象调用的 object_getClass 得到的是该类对象的 meta-class,如 obj2 和 obj4,而通过实例对象调用的object_getClass 得到的是该实例对象的类对象,如 obj1,objc_getClass 这个方法获取是实例对象的类对象,与object_getClass 还是有点不一样的。而 objc_getMetaClass 可以直接获取 meta-class,如 obj3。

总之:
1、objc_getClass 参数是类名的字符串,返回的就是这个类的类对象。
2、object_getClass 参数是 id 类型,它返回的是这个 id 的 isa 指针所指向的Class;如果传参是Class,则返回该Class的meta-class。

在 NSObject.mm 中,可以看到 self 和 class 方法都要实例和类方法,class 方法返回的都是类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (id)self 
{
return (id)self;
}

- (id)self
{
return self;
}

+ (Class)class
{
return self;
}

- (Class)class
{
return object_getClass(self);
}

所以,无论是类还是实例调用 class 方法,返回的都是同一个 class object,举例:

1
2
3
4
5
6
Class objClz1 = [NSObject class];
Class objClz2 = [[[NSObject alloc] init] class];

if (objClz1 == objClz2) {
NSLog(@"objClz1: %@, ->%p", objClz1, objClz1);
}

输出结果是:

1
objClz1: NSObject, ->0x10fa30f38

isa

下面的例子来源自 这里,感谢 kingizz’s blog,代码中 Son 是 Father 的子类,而 Father 是 NSObject 的子类。

1
2
3
@interface Father : NSObject

@end
1
2
3
@interface Son : Father

@end

我们结合下面这个图来理解一下,子类、父类、元类以及 isa 指针。

一个实例对象的 isa 指向对象所属的类,这个类的 isa 指向这个类的元类,而这个元类的 isa 又指向 NSObject 的元类,NSObject 的元类的 isa 指向其本身,最终形成形成一个完美的闭环。

在OC中,所有的对象都有一个 isa 指针,指向对象所属的类,类也是一个对象,类对象的 isa 指针指向类的元类。

参考文章

1、Objective-C 中的对象、类、元类

2、Objective-C Runtime(一)对象模型及类与元类

3、被误解的 objc_class


扫码关注,期待与你的交流~

被误解的 objc_class

发表于 2019-01-28 | 分类于 iOS |

网上绝大多数的博客讲 objc_class 的定义,基本上都使用了下面的代码一来讲解,与 objc4 源码 objc-runtime-new.h 中关于 objc_class 中的定义完全不一样,我认真地去探究了一下,发现这个世界上实属雷同的事件还是蛮多的,老实做事做学问的人少的可怜!

本文的所涉及到的 objc4 源码,截止到写本文最新的是 objc4-750 这个版本。

代码一:简洁版也称坑货版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

在上面的代码中 OBJC2_UNAVAILABLE 看起来让人觉得有点奇怪,从字面意思上可以理解为在OC2.0版本不可用了,还有一个 OBJC_ISA_AVAILABILITY 是在表示 Objective-C 都可以使用吗?

在 objc-api.h 中有关于这两个宏的定义,如下:

代码二:关键宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable 
* in the future */
#if !defined(OBJC_ISA_AVAILABILITY)
# if __OBJC2__
# define OBJC_ISA_AVAILABILITY __attribute__((deprecated))
# else
# define OBJC_ISA_AVAILABILITY /* still available */
# endif
#endif

/* OBJC2_UNAVAILABLE: unavailable in objc 2.0, deprecated in Leopard */
#if !defined(OBJC2_UNAVAILABLE)
# if __OBJC2__
# define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
# else
/* plain C code also falls here, but this is close enough */
# define OBJC2_UNAVAILABLE \
__OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \
__IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__") \
__TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE
# endif
#endif

从定义来看,OBJC_ISA_AVAILABILITY 在OC2.0版本中标示已经过时了,OBJC2_UNAVAILABLE 标示在OC2.0中已经不可用了,将来会被移除的。

我们不妨来摘录完整的代码,如下:

代码三:完整版也称整明白版

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
#if !OBJC_TYPES_DEFINED

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

/// An opaque type that represents a category.
typedef struct objc_category *Category;

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

#endif

这里居然还有个宏 OBJC_TYPES_DEFINED,看一下其在 objc-private.h 中的定义,如下:

1
#define OBJC_TYPES_DEFINED 1

那么 #if !OBJC_TYPES_DEFINED 已经限制了其到 #endif 中间的代码都是无效的,所以关于代码一处的代码其实已经没有实际意义了,网上的朋友们请不要拿这段代码再 骗人 了。

源码 objc-runtime-new.h 中关于 objc_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
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

// ....
}

struct objc_object {
private:
isa_t isa;
// ...
}

union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

无论是学知识还是做知识,老实认真应该是最基本的要求,千万不要以讹传讹,误人子弟!


只要你想做,总会有办法的~

集合对象可变与不可变的那点事

发表于 2019-01-13 | 分类于 iOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。


简介

在文章 NSString NSMutableString 可变与不可变的那些事儿 分享了关于 NSString 和 NSMutableString 与 copy 以及 mutableCopy 之间的点滴。

今天跟大家分享一下集合类数据的可变与不可变性,再结合 copy 以及 mutableCopy 说一说注意事项。如果你仔细看过 NSString NSMutableString 可变与不可变的那些事儿 这篇文章,那么接下来看本篇会很轻松。

本篇内容主要涉及以下几个方面:

  • 在 OC 中的集合对象
  • 集合对象的 copy、mutableCopy
  • 可变与不可变集合对象之间等号赋值
  • property 中的集合对象的 copy 和 strong
  • 实际案例分析

为了说明问题,这里,我选用数组(NSArray)作为集合对象的代表,其他的集合类以此类推即可。

集合对象

在 Objective-C 中,非集合类对象指的是 NSString、NSNumber、NSValue 之类的对象,除了 NSString 有对应的可变类 NSMutableString 外,NSNumber、NSValue 都没有可变类与其对应。

集合类对象是指 NSArray、NSMutableArray、 NSDictionary、NSMutableDictionary、NSSet、NSMutableSet 之类的对象。

集合对象的 copy、mutableCopy

看一个具体例子,请接着看下面的示例代码和说明。

例子1:NSArray 的 copy、mutableCopy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSArray *array = [NSArray arrayWithObjects:@"veryitman.com", nil];
NSLog(@"array addr: %p, array: %@ ", array, array);

// 地址未变和array一致,内容也一致
NSArray *array1 = array;
NSLog(@"array1 addr: %p, array1: %@", array1, array1);

// 地址未变和array一致,内容也一致
// copy 之后仍然是不可变的数组对象
id array2 = [array copy];
NSLog(@"array2 addr: %p, array2: %@", array2, array2);

// 地址改变
id array3 = [array mutableCopy];
NSLog(@"array3 addr: %p, array3: %@", array3, array3);

// 进一步说明了经过mutableCopy后,array3变成了可变数组
[(NSMutableArray *)(array3) addObject:@"my blog"];
NSLog(@"array3 addr: %p, array3: %@", array3, array3);

// 因为array3地址变了,不会影响array的地址和值
NSLog(@"array addr: %p, array: %@ ", array, array);

小结 1:

1、不可变数组 copy 之后,仍然是不可变数组,其地址和内容不变,即拷贝了原对象的内容和指针,属于指针拷贝。

2、不可变数组 mutableCopy 之后,变成了可变数组,其地址发生了变化,即只拷贝了原对象的内容,指针没有拷贝,属于内容拷贝。

3、不可变数组之间的等号(=)赋值,是指针拷贝。

例子2:NSMutableArray 的 copy、mutableCopy

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
NSMutableArray *marray = [NSMutableArray arrayWithObjects:@"veryitman.com", nil];
NSLog(@"marray addr: %p, marray: %@ ", marray, marray);

// 地址未变和marray一致,内容也一致
NSMutableArray *marray1 = marray;
NSLog(@"marray1 addr: %p, marray1: %@ ", marray1, marray1);

// copy 之后,地址改变且变成了不可变的数组对象
id marray2 = [marray copy];
NSLog(@"marray2 addr: %p, marray2: %@ ", marray2, marray2);

// mutableCopy 之后,地址改变但仍是可变数组对象
id marray3 = [marray mutableCopy];
NSLog(@"marray3 addr: %p, marray3: %@ ", marray3, marray3);

// Crash:进一步说明了可变数组对象经过 copy 之后变成了不可变的marray2
// -[__NSSingleObjectArrayI addObject:]: unrecognized selector sent to instance 0x600002cbd320
// *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
// reason: '-[__NSSingleObjectArrayI addObject:]: unrecognized selector sent to instance 0x600002cbd320'
// [(NSMutableArray *)(marray2) addObject:@"my blog"];
// NSLog(@"marray2 addr: %p, marray2: %@ ", marray2, marray2);

// 进一步证明了mutableCopy 之后,marray3是可变数组
[(NSMutableArray *)(marray3) addObject:@"my blog"];
NSLog(@"marray3 addr: %p, marray3: %@ ", marray3, marray3);

// 因为marray3地址改变了,所以对marray3的操作不会影响原来的数组对象marray
// marray 地址和内容保持不变
NSLog(@"marray addr: %p, marray: %@ ", marray, marray);

小结 2:

1、可变数组 copy 之后,会变成不可变数组,其内容不变,但是地址改变了,即只拷贝了原对象的内容,没有进行指针拷贝,属于内容拷贝。

2、可变数组 mutableCopy 之后,仍然是不可变数组,其地址发生了变化,内容没有变化,即只拷贝了原对象的内容,指针没有拷贝,属于内容拷贝。

3、可变数组之间等号(=)赋值,是指针拷贝。

例子3:NSMutableArray 和 NSArray 之间等号赋值

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
/** 向不可变数组赋值可变数组 */
{
NSMutableArray *tDatas = [NSMutableArray arrayWithObjects:@"veryitman.com", nil];
NSLog(@"--1--- tDatas addr: %p, tDatas: %@", tDatas, tDatas);

// 类似但不同于可变数组的mutableCopy操作,此时 array 的地址未变和tDatas地址一致
// array的内容和地址未发生变化,和tDatas一致
NSArray *array = tDatas;
NSLog(@"--2--- array addr: %p, array: %@", array, array);
}

/** 向可变数组赋值不可变数组 */
{
NSArray *tDatas = [NSArray arrayWithObjects:@"veryitman.com", nil];
NSLog(@"--1--- tDatas addr: %p, tDatas: %@", tDatas, tDatas);

// 类似进行了不可变数组的 copy 操作
// array 仍旧是不可变的,地址和内容与tDatas一致
NSMutableArray *array = tDatas;
NSLog(@"--2--- array addr: %p, array: %@", array, array);

// crash: 还是不可变的数组
// -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x6000025499c0
// Terminating app due to uncaught exception 'NSInvalidArgumentException',
// reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x6000025499c0'
// [array addObject:@"blog"];
}

输出结果,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
--1--- tDatas addr: 0x6000023a8bd0, tDatas: (
"veryitman.com"
)
--2--- array addr: 0x6000023a8bd0, array: (
"veryitman.com"
)

--1--- tDatas addr: 0x600002dbbf00, tDatas: (
"veryitman.com"
)
--2--- array addr: 0x600002dbbf00, array: (
"veryitman.com"
)

以上是使用 NSArray、NSMutableArray 来进行测试的,NSDictionary 和 NSSet 以及其对应的可变类型都遵循上面总结的内容。

copy、strong 修饰属性

在属性中,我们如何来选择 copy 或者 strong 来作为集合数据的修饰语呢?

根据上面示例分析结果可以看出,在属性中,如果使用 strong 修饰不可变数组,那么在使用过程中(被可变数组赋值)该不可变数组有可能会变为可变数组。如果使用 copy 修饰可变数组,那么在使用过程中(被不可变数组赋值)该可变数组有可能变为不可变数组。

小结 3:

当修饰可变类型的属性时,如 NSMutableArray、NSMutableDictionary、NSMutableSet 等集合类型时,用 strong 修饰。

当修饰不可变类型的属性时,如 NSArray、NSDictionary、NSSet 等集合类型时,用 copy 修饰。

大家如果有兴趣可以参考文章 NSString NSMutableString 可变与不可变的那些事儿 的做法来验证上面的理论知识。

实际案例分析

再给大家举个实际的开发案例,我们需要定时上报目采集APP的数据,这个需求看起来是没有任何难度的。

我们使用代码来模拟一下上报数据的这个过程。

1
2
3
4
5
6
7
// 采集到的数据
NSMutableDictionary *tDatas = [NSMutableDictionary dictionaryWithCapacity:5];
[tDatas setObject:@"https://" forKey:@"req_m"];
NSLog(@"--采集数据--- tDatas addr: %p, tDatas: %@", tDatas, tDatas);
// 开始发送
[self sendDatas:tDatas];
NSLog(@"--上报完成,原数据--- tDatas addr: %p, tDatas: %@", tDatas, tDatas);

发送数据的模拟示例如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)sendDatas:(NSDictionary *)datas
{
NSLog(@"--上报中--- datas addr: %p, datas: %@", datas, datas);

/** 下面两行代码只是为了模拟原数据被外界在传输过程中被改变,比如其他采集线程改变了它 */
if ([datas isKindOfClass:[NSMutableDictionary class]]) {
[(NSMutableDictionary *)datas setObject:@"veryitman.com" forKey:@"test_m"];
}

NSLog(@"--上报完成--- datas addr: %p, datas: %@", datas, datas);
}

根据上面例子3提到的,不可变向可变等号赋值时,原不可变对象会变成可变对象。

控制台输出日志,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--采集数据--- tDatas addr: 0x600000b788e0, tDatas: {
req_m = https://;
}

--上报中--- datas addr: 0x600000b788e0, datas: {
req_m = https://;
}

--上报完成--- datas addr: 0x600000b788e0, datas: {
req_m = https://;
test_m = veryitman.com;
}

--上报完成,原数据--- tDatas addr: 0x600000b788e0, tDatas: {
req_m = https://;
test_m = veryitman.com;
}

下面代码的代码,我是为了模拟原数据被其他代码改变了的情况,只是为了说明,不可变对象容易被外界影响和改变。

1
2
3
4
/** 下面两行代码只是为了模拟原数据被外界在传输过程中被改变,比如其他采集线程改变了它 */
if ([datas isKindOfClass:[NSMutableDictionary class]]) {
[(NSMutableDictionary *)datas setObject:@"veryitman.com" forKey:@"test_m"];
}

上面的总结又提到无论是可变对象还是不可变对象经过 copy 之后都是不可变对象的原理,我们修改一下代码,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)sendDatas:(NSDictionary *)datas
{
NSDictionary *copy_datas = [datas copy];

NSLog(@"--上报中--- copy_datas addr: %p, copy_datas: %@", copy_datas, copy_datas);

if ([copy_datas isKindOfClass:[NSMutableDictionary class]]) {
[(NSMutableDictionary *)copy_datas setObject:@"veryitman.com" forKey:@"test_m"];
} else {
NSLog(@"Yes, copy_datas 是不可变字典。");
}

NSLog(@"--上报完成--- copy_datas addr: %p, copy_datas: %@", copy_datas, copy_datas);
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 --采集数据--- tDatas addr: 0x600003b08740, tDatas: {
req_m = https://;
}

--上报中--- copy_datas addr: 0x600003b08700, copy_datas: {
req_m = https://;
}

Yes, copy_datas 是不可变字典。

--上报完成--- copy_datas addr: 0x600003b08700, copy_datas: {
req_m = https://;
}

--上报完成,原数据--- tDatas addr: 0x600003b08740, tDatas: {
req_m = https://;
}

扫码关注,期待与你的交流~

macOS下生成字符串md5

发表于 2019-01-13 | 分类于 MacOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。


在 macOS 系统下生成任意字符串的 md5 值,很简单,直接使用 md5 命令即可。

例如,要生成 veryitman.com 这个字符串的md5值,直接使用下面的命令即可,示例如下:

1
md5 -s veryitman.com

对应生成的结果如下:

1
MD5 ("veryitman.com") = c5c401dcdacd95052eef360c3533a8bd

这里要注意,有些使用者会这样来计算:

1
echo "veryitman.com" | md5

生成结果如下:

1
bc239ea4ba4ebbd4ef9e61c160fcac3c

发现和上面的结果不一致,这是因为 echo 默认会添加一个换行符,导致计算的md5值不一致,修改一下:

1
echo -n "veryitman.com" | md5

生成结果 c5c401dcdacd95052eef360c3533a8bd 与上面一致了。

注意: echo -n 用来不显示结尾的换行符。

更多关于 md5 的命令 可以问男人(man),如下:

1
man md5

MD5(Message-Digest Algorithm 5) 全称是报文摘要算法,此算法对任意长度的信息逐位进行计算,产生一个二进制长度为128位(十六进制长度就是32位)的“指纹”(或称“报文摘要”),不同的文件也能产生相同的报文摘要,但是可能性是极其小的。

MD5 算法 常常被用来验证网络文件传输的完整性,防止文件被人篡改,但是现在 MD5 的算法并不安全了。

在 macOS 上面还有 md5sum 这个命令,一般用来计算文件的md5值。


扫码关注,期待与你的交流~

iOS 中如何使用对象的弱引用

发表于 2019-01-06 | 分类于 iOS |

简介

我们都知道使用 UIImage imageNamed 创建的 UIImage 对象会被持有(强引用),如果图片太大会占用内存,损耗 APP 的性能,影响用户体验,如果能改造对其的强引用变为弱引用就可以解决问题。

我们可能会有类似上面的场景,有些对象暂时保存起来,可能后面会用到,也有可能不会使用,但是又不想去管理它们的生命周期,如果它们能够自己被销毁就很省事,不需要去关心这些对象到底耗费了多少内存。

今天跟大家聊聊如何在 iOS 开发中保持对对象的弱引用而不是强引用,希望看完之后,能帮助到大家去解决实际问题。

NSObject retainCount

在 iOS 中创建一个对象,该对象的引用计数就会加1,例如下面的例子:

1
2
NSObject *obj = [NSObject alloc] init];
NSLog(@"obj retain count: %zd", [obj retainCount]);

上面的例子输出是1,当然在 ARC 下是无法使用 retainCount 这个方法的,只有在非 ARC 条件下才可以,如果要运行上面的例子,对应的文件需要设置为 -fno-objc-arc.

1
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

可以在 usr/include/objc/NSObject.h 中查看,retainCount 是 NSObject 协议(@protocol NSObject)中定义的一个方法,而 NSObject 类是实现了该协议的,如下:

1
@interface NSObject <NSObject>

所以,任何OC对象都具有 retainCount 方法。另外,你添加一个视图,视图其实也是被容器引用了,其计数也会加1被容器持有其强引用,再例如在数组中添加一个对象,会使对象的引用计数加1,被数组所持有。

NSValue valueWithNonretainedObject

在 iOS 中,NSValue 的类方法 valueWithNonretainedObject 可以保持对对象的弱引用。

1
+ (NSValue *)valueWithNonretainedObject:(nullable id)anObject;

This method is useful if you want to add an object to a Collection but don’t want the collection to create a strong reference to it.

大概意思是,该方法可以不持有对象的强引用,换句话说,只持有对象的弱引用。

举个栗子~

MZDog.h

1
2
3
@interface MZDog : NSObject

@end

MZDog.m

1
2
3
4
5
6
7
8
9
10
#import "MZDog.h"

@implementation MZDog

- (NSString *)description
{
return [NSString stringWithFormat:@"MZDog-obj retain count: %zd", [self retainCount]];
}

@end

这里 MZDog 是设置了非 ARC 的,如图:

在测试文件中使用 MZDog,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 对 dog 使用弱引用,此时其引用计数还是1
NSValue *value = [NSValue valueWithNonretainedObject:dog];
NSLog(@"dog: %@, value: %@", dog, value);

// 获取 value 对应的对象
id obj = value.nonretainedObjectValue;
NSLog(@"obj isKindOfClass MZDog: %i", [obj isKindOfClass:[MZDog class]]);

if (obj == dog) {
NSLog(@"The obj is same dog object.");
}

对应的控制台输出,如下:

1
2
3
4
dog: MZDog-obj retain count: 1
dog: MZDog-obj retain count: 1, value: <308cf600 00600000>
obj isKindOfClass MZDog: 1
The obj is same dog object.

从上面的例子可以看出,valueWithNonretainedObject 对 MZDog 对象 dog 是没有强应用的。修改代码,示例一下:

1
2
3
4
5
6
7
8
9
10
11
12
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 对 dog 使用弱引用,此时其引用计数还是1
NSValue *value = [NSValue valueWithNonretainedObject:dog];
NSLog(@"dog: %@, value: %@", dog, value);

// 经过NSValue包装后,可以放到对应的集合对象(如数组,字典等)中,这样这些集合就不会对 dog 进行强引用了
NSArray *array = [NSArray arrayWithObjects:value, nil];
// dog 的引用计数还是1
NSLog(@"dog: %@, array: %@", dog, array);

对应的输出日志:

1
2
3
dog: MZDog-obj retain count: 1
dog: MZDog-obj retain count: 1, value: <40b7a401 00600000>
dog: MZDog-obj retain count: 1, array: ("<40b7a401 00600000>")

方法 valueWithNonretainedObject 等同于

1
NSValue *theValue = [NSValue value:&anObject withObjCType:@encode(void *)];

上面的示例,可以改写一下:

1
2
3
4
5
6
7
8
9
10
11
12
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 对 dog 使用弱引用,此时其引用计数还是1
NSValue *value = [NSValue value:&dog withObjCType:@encode(void *)];
NSLog(@"dog: %@, value: %@", dog, value);

// 经过NSValue包装后,可以放到对应的集合对象(如数组,字典等)中,这样这些集合就不会对 dog 进行强引用了
NSArray *array = [NSArray arrayWithObjects:value, nil];
// dog 的引用计数还是1
NSLog(@"dog: %@, array: %@", dog, array);

输出日志:

1
2
3
dog: MZDog-obj retain count: 1
dog: MZDog-obj retain count: 1, value: <40568a02 00600000>
dog: MZDog-obj retain count: 1, array: ("<40568a02 00600000>")

此时 dog 的引用计数还是没有增加~

自写弱引用的集合分类

根据上面的理论知识,我们可以使用 NSValue 写出弱引用的集合对象,思路很简单,创建集合类的分类,然后使用 NSValue 来进行包装。看下面的示例代码即可。

NSArray+MZWeak.h

1
2
3
4
5
6
7
8
9
10
11
@interface NSArray (MZWeak)

- (id)mz_weak_objectAtIndex:(NSUInteger)index;

@end

@interface NSMutableArray (MZWeak)

- (void)mz_weak_addObject:(id)obj;

@end

NSArray+MZWeak.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
#import "NSArray+MZWeak.h"

@implementation NSArray (MZWeak)

- (id)mz_weak_objectAtIndex:(NSUInteger)index
{
NSValue *value = [self objectAtIndex:index];

return value.nonretainedObjectValue;
}

@end

@implementation NSMutableArray (MZWeak)

- (void)mz_weak_addObject:(id)obj
{
NSValue *value = [NSValue valueWithNonretainedObject:obj];
if (nil != value) {
[self addObject:value];
}
}

@end

在文件中使用,示例如下:

1
2
3
4
5
6
7
8
9
10
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
// 弱引用
[array mz_weak_addObject:dog];

// 此时 dog 的引用计数还是1
NSLog(@"dog: %@", dog);

依次类推,对于其他集合类 NSDictionary、NSSet 都可以实现。当然实现方式不止这一种,这里只是举了一个 NSValue 包装对象来实现的例子。

当然你也可以使用 NSProxy 或者 block 来解除对对象的强引用。关于 block 的解除方法,可以参考开源项目 HXImage,另外开源项目 YYWeakProxy 里面使用了 NSProxy 来解除强引用。

那么,除了上面提到的方法,系统类库中有没有现成的类呢?聪明的你一定猜到了,一定有!

是的,往下看。。。

NSPointerArray、NSMapTable、NSHashTable

集合类 NSArray、NSDictionary 和 NSSet 以及其对应的可变版本,都可以用来存储 OC对象的, 但是对其中的对象都是强引用的。

从 iOS6.0 版本及以后的版本中,系统给我们提供了 NSPointerArray、NSMapTable 和 NSHashTable 分别对应 NSArray、NSDictionary 和 NSSet,最大的不同就是,NSPointerArray、NSMapTable 和 NSHashTable 对对象是弱引用而不是强引用。

现在大部分的 iOS APP 或者 iOS 游戏应该都至少在 iOS7 以上了吧,所以可以尽情使用这些系统提供的类库了。

使用 NSPointerArray 保存弱引用的对象,需要使用下面三种方式来创建 NSPointerArray 对象,如下:

1
2
3
4
5
6
7
8
// 创建 NSPointerArray 对象方式一
NSPointerArray *pointerArray = [NSPointerArray weakObjectsPointerArray];

// 创建 NSPointerArray 对象方式二
NSPointerArray *pointerArray1 = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];

// 创建 NSPointerArray 对象方式三
NSPointerArray *pointerArray2 = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];

那么下面还是以 MZDog 来举例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 创建 NSPointerArray 对象方式一
// 注意 weakObjectsPointerArray 而不是 strongObjectsPointerArray
NSPointerArray *pointerArray = [NSPointerArray weakObjectsPointerArray];
[pointerArray addPointer:(__bridge void *)(dog)];

// 创建 NSPointerArray 对象方式二
NSPointerArray *pointerArray1 = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
[pointerArray1 addPointer:(__bridge void *)(dog)];

// 创建 NSPointerArray 对象方式三
NSPointerArray *pointerArray2 = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];
[pointerArray2 addPointer:(__bridge void *)(dog)];

// dog 引用计数还是1
NSLog(@"dog: %@", dog);

对应的输出 dog 对象的 retainCount 仍然是 1,其引用计数没有增加。

对应 NSMapTable 和 NSHashTable 的示例如下:

NSMapTable 示例

1
2
3
4
5
6
7
8
9
10
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 弱应用对象
NSMapTable *map = [NSMapTable weakToWeakObjectsMapTable];
[map setObject:dog forKey:@"first"];

// 引用计数还是1,没变
NSLog(@"dog: %@", dog);

NSHashTable 示例

1
2
3
4
5
6
7
8
9
10
// retainCount -> 1
MZDog *dog = [MZDog new];
NSLog(@"dog: %@", dog);

// 弱应用对象
NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
[hashTable addObject:dog];

// 引用计数还是1,没变
NSLog(@"dog: %@", dog);

NSPointerArray 与 NULL

在 NSMutableArray 中添加的对象不可以是 nil,而 NSPointerArray 中却可存储 NULL(nil 经过转换得到C指针为 NULL),也可以用来存储weak对象。weak类型的对象释放之后,NSPointerArray 的对应位置会自动变成 NULL,使用count 属性, 会将 NULL 元素也计算进来,即 NULL 算是它的一员。下面示例可以证明,如下:

1
2
3
4
5
6
7
8
9
10
11
12
MZDog *dog = nil;

NSPointerArray *pointerArray = [NSPointerArray weakObjectsPointerArray];
void *cobj = (__bridge void *)(dog);
NSLog(@"obj: %@", cobj); //NULL
[pointerArray addPointer:cobj];

// 虽然存储的是 NULL,但是 count 仍然是 1
NSLog(@"pointerArray count: %zd", [pointerArray count]);

NSArray *array = [pointerArray allObjects];
NSLog(@"pointerArray allObjects: %@", array);

一般这样删除 NSPointerArray 中的 NULL 元素,如下:

1
2
[pointerArray addPointer:NULL];
[pointerArray compact];

这里要注意,将OC对象转换为C指针要使用 (__bridge void *) 这种方式,不要使用 (__bridge_retained void *) 或者 CFBridgingRetain,这二者会对 dog 对象进行强引用。如下示例:

1
2
3
4
5
6
7
8
9
10
// retainCount -> 1
MZDog *dog = [MZDog new];
NSPointerArray *pointerArray = [NSPointerArray weakObjectsPointerArray];
// 这里会 retain dog 对象,使其引用计数加1,此时retainCount 是 2
[pointerArray addPointer:(__bridge_retained void *)dog];
// 这里会 retain dog 对象,使其引用计数再加1,retainCount 是 3
[pointerArray addPointer:CFBridgingRetain(dog)];

// 此时的 retainCount 是 3
NSLog(@"dog: %@", dog);

如果你对 (__bridge_retained void *) 或者 CFBridgingRetain 感兴趣,可以看看 C 指针与 OC 对象之间的转换 这篇文章。


扫码关注,你我就各多一个朋友~

2018,不会重来

发表于 2018-12-31 | 分类于 随笔 |

今天是2018年的最后一天,即2018年12月31日。提前祝愿所有人2019身体健康,万事如意,阖家幸福!

这篇文章 再见 2016 是在2016年末写的,算是一个hin随心的小结。比较遗憾的是在2017年没有给自己写个总结,所以,不能再错过2018了,我怕老了没有可以寻迹的回忆!

2018 有太多需要感谢的人了,感谢所有工作上,生活上关心、支持和鼓励我的人们,感谢我的家人给予了我太多的理解和包容,非常感谢你们!

工作

2018年我的职业没有太大的变化,仍然是奋斗在一线工作岗位上的一名软件工程师,我喜欢这份职业,它没有过多的纷争,工程师之间的沟通简单、直接!

唯一和以前不同的是自己开始负责一个团队了,责任比以前要大了很多,压力也随之剧增。慢慢地,写代码变成了可望不可求的事情了,因为你要处理比写代码更重要的事情,比如团队的磨合,目标的制定,任务的规划等等一系列工作。

在 《蚂蚁金服:科技金融独角兽的崛起》 这本书里讲到过:

作为领导,团队里每一个人的错误都是自己的错误,但团队里每一个人的成果未必是自己的成果,这时整个人的心态就需要重新调整。

我算是一个比较幸运的人,因为团队中有很多比自己更优秀的人,他们有想法,有执行,总是能带动其他人积极的工作。这些人是团队学习的榜样,更是我们团队的财富。

公司在改变,各个方面也越来越成熟,无论是公司文化还是技术能力都在步步高升,今年尤为看到的是 CTO 对技术中台下定的一些决心,这是让人兴奋的。公司一直在强调赋能,把更多优秀、有担当、有责任心的人提拔上来,给他们配备资源,以便发挥他们更大的潜能,大家工作的热情也更上一层楼,因为公司相信他们。

很多时候,并不是你身边没有优秀的人,只是你不愿意相信他们,前怕狼后怕虎的心态阻止了这些优秀人的发挥。其实,他们只是需要你一个肯定的答复,所以试着去改变自己,相信团队里面的每个人,给他们更多的肯定,鼓励和支持他们,结果不会让你失望的。

生活

自从老婆孩子回老家之后,我就过上了“单身“的生活,每天早出晚归,努力用工作来代替对他们的思念。

我家的大宝是个天生的运动狂,感觉他总是有用不完的力气,除非他自己在拼图或者画画,否则你甭想清静。一会在家里穿上溜冰鞋给你表演各种溜冰技能,一会拿起篮球给你表演球技,再不是就让你陪他玩跳棋。

老婆大人在家待产了,自己一个人挺着肚子还要给大宝做饭,每天接送他去学校,知道她一个人在家不容易,我也经常鼓励她,她说:“为了孩子,这点苦不算什么!”。有时候,想一想二宝快要跟我们见面了,挺兴奋的。

有时候挺想念他们娘俩的,工作不忙的时候我也会请假回老家,记得上次回去离开的时候,儿子问了我一个问题,他说:“爸爸,你说是钱重要,还是人重要?”,我当时愣了一下,告诉他:“当然是人重要呀!”,他不开心的点了点头。显然,这家伙对我的离开很不满意,那天晚上我赶火车走的时候,大宝在被窝里哭了很久,其实,我也流泪了,只是不愿意承认罢了!

你自己除了是一名员工之外,还是一名儿子、女儿,或者是一名父亲、母亲,一名丈夫、妻子,只有处理好生活上面的事情,才能更好的投入到工作当中来。无论如何,人还是需要有梦想的!我的梦想就是努力学习更多有用的知识,然后用自己的知识去教育自己的子女,让他们将来能有更好的生活,做一个有用的社会人。

用电影《中国合伙人》成东青的一句话来说:“梦想是什么,梦想就是一种让你感到坚持就是幸福的东西!”。

阅读

这些年,自己唯一没有丢弃的爱好就是阅读。

工作上的忙碌,生活上的疲惫,很多时候让我们无法静下心来去阅读,甚至有时候会觉得阅读简直在浪费时间,浪费生命。如果你有这种想法,建议你请假去好好休息几天,抛开喧嚣的尘世,放空一下自己。

我一直坚持阅读,无论是技术书籍还是人文历史,抑或人物传记,平时工作也很忙,我就利用零散的时间来阅读,就算这样,每周每个月累计下来阅读量也不少了,随着阅读量的增多,感觉自己的气色好了很多,因为心态好了,遇事比以前更加沉着冷静了,也更加理性了。

您的气质里藏着你读过的那些书。

今年读到自认为不错的书籍,推荐给大家:

1. 蚂蚁金服:科技金融独角兽的崛起

2. 赋能:打造应对不确定性的敏捷团队

3. 我的情绪为何总被他人左右

4. 终身成长

2019

1. 加强对上沟通

沟通不能仅限于对下沟通,对上沟通尤为重要,让上级知道目前项目的进度和规划,以及遇到的问题。这一点自己做的还不够好,在2019年要加强。

2. 加强自我管理

管理,不是管理别人,而是要管理好自己,没有人愿意被管理,如果有需要被严格管理的,他可能不适合在你的团队中生存。加强自我管理是我们每个人的目标。

3. 个人、团队技术能力再上一层楼

补齐自己的短板,想尽一切办法提高自己和团队其他技术人员的技术能力,在稳定、高效的同时寻找更多自我成长的途径和方法。

4.更加关心自己的家人和身边的朋友

没有家人的支持和鼓励,你很难去投入工作,所以要好好的对待自己的家人,多关心他们,家人对你的要求不多,往往只需要知道你有没有惦记他们就够了。当然了,我很期待和我们家的二宝见面,嘿嘿😜!

朋友多了,路才好走,记得跟朋友多聊天,多听听他们的故事,最重要的是经常约他们喝喝酒。


最后分享给大家一句话,共勉:

你的目的不是给谁打工,而是成为更好的自己,建设更好的未来!所以尽快去调整心态,停止抱怨,立即行动,积极沟通!


扫码关注,你我就各多一个朋友~

<1…91011…20>

193 日志
16 分类
163 标签
© 2024 veryitman