三味猪屋


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签
三味猪屋

消除第三方library警告

发表于 2017-06-14 | 分类于 clang

集成第三方库的时候经常会报一些警告,如:
1、unable to open object file: No such file or directory

因为在build静态lib时,CLANG_ENABLE_MODULES没有被设置成NO。

解决办法:

1
2
3
1) Go to Build Settings -> Build Options -> Debug Information Format
2) Change the Debug setting from "DWARF with dSYM File" to "DWARF"
3) Leave the Release setting at "DWARF with dSYM File"

但是这不是根本解决办法,根本的还是需要静态库提供者去消除这个警告。

2、ld: pointer not aligned at address 0x13735E9


解决办法:

参考:
https://pewpewthespells.com/blog/buildsettings.html#clang_enable_modules
https://stackoverflow.com/questions/42268117/how-to-remove-the-xcode-warning-apple-mach-o-linker-warning-pointer-not-aligned/43056194#43056194

三味猪屋

iOS包大小瘦身方案(二)- 基于clang插件iOS瘦身实践

发表于 2017-06-02 | 分类于 app-thinning

clang作为LLVM提供的编译器前端,可将用户的源代码(C/C++/Objective-C)编译成语言/目标设备无关的IR(Intermediate Representation)实现。其可提供良好的插件支持,容许用户在编译时,运行额外的自定义动作。
本篇是基于clang的插件特性,编写一个clang插件作为一个源代码级别的分析工具(或编译器)生成各种中间文件。编译完成后,还需编写一个工具去分析所有包含源码的方法(包括用户编写,以及引入的第三方库源代码),检查这些方法中哪些最终可被程序主入口调用,剩余即是疑似无用代码。从而分析出源代码中那些确定无用的代码,便可以有效去除无用的代码从而减少包大小。
LLVM工程包含了一组模块化,可复用的编辑器和工具链:

1
2
3
4
5
6
7
8
9
10
11
12
13
LLVM Core:包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。
Clang:一个C/C++/Objective-C编译器,致力于提供令人惊讶的快速编译,极其有用的错误和警告信息,提供一个可用于构建很棒的源代码级别的工具.
dragonegg: gcc插件,可将GCC的优化和代码生成器替换为LLVM的相应工具。
LLDB:基于LLVM提供的库和Clang构建的优秀的本地调试器。
libc++、libc++ ABI: 符合标准的,高性能的C++标准库实现,以及对C++11的完整支持。
compiler-rt:针对__fixunsdfdi和其他目标机器上没有一个核心IR(intermediate representation)对应的短原生指令序列时,提供高度调优过的底层代码生成支持。
OpenMP: Clang中对多平台并行编程的runtime支持。
vmkit:基于LLVM的Java和.NET虚拟机实
polly: 支持高级别的循环和数据本地化优化支持的LLVM框架。
libclc: OpenCL标准库的实现
klee: 基于LLVM编译基础设施的符号化虚拟机
SAFECode:内存安全的C/C++编译器
lld: clang/llvm内置的链接器


原文链接

1、clang源码并编译安装
查看clang源码并编译安装
2、编写clang插件
要实现自定义的Clang插件(以C++为例),应按照以下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.自定义继承自
clang::PluginASTAction(基于consumer的AST前端Action抽象基类)
clang::ASTConsumer(用于客户读取AST的抽象基类),
clang::RecursiveASTVisitor(前序或后续地深度优先搜索整个 AST,并访问每一个节点的基类)等基类,
2.根据自身需要重载
PluginASTAction::CreateASTConsumer,
PluginASTAction::ParseArgs,
ASTConsumer::HandleTranslationUnit
RecursiveASTVisitor::VisitDecl
RecursiveASTVisitor::VisitStmt
等方法,实现自定义的分析逻辑。
3.注册插件
static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin-name", "my-plugin-description");
4.编译时载入插件
clang++ *** -Xclang -load -Xclang path-of-your-plugin.dylib -Xclang -add-plugin -Xclang your-pluginName -Xclang -plugin-arg-your-pluginName -Xclang your-pluginName-param

编写clang插件:

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
#include <iostream>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/AST/RecursiveASTVisitor.h"
using namespace clang;
using namespace std;
using namespace llvm;
namespace ClangPlugin
{
class ClangPluginASTVisitor : public RecursiveASTVisitor<ClangPluginASTVisitor>
{
private:
ASTContext *context;
public:
void setContext(ASTContext &context){
this->context = &context;
}
bool VisitDecl(Decl *decl){
if(isa<ObjCImplDecl>(decl)){
ObjCImplDecl *interDecl = (ObjCImplDecl*)decl;
cout<<"[KWLM]Class Implementation Found:"<<interDecl->getNameAsString()<<endl;
}
return true;
}
};
class ClangPluginASTConsumer : public ASTConsumer
{
private:
ClangPluginASTVisitor visitor;
void HandleTranslationUnit(ASTContext &context){
visitor.setContext(context);
visitor.TraverseDecl(context.getTranslationUnitDecl());
}
};
class ClangPluginASTAction : public PluginASTAction
{
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,StringRef InFile){
return unique_ptr<ClangPluginASTConsumer>(new ClangPluginASTConsumer);
}
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string>& args){
return true;
}
};
}
static clang::FrontendPluginRegistry::Add<ClangPlugin::ClangPluginASTAction>X("ClangPlugin", "ClangPlugin");

编译生成插件(dylib):

1
clang -std=c++11 -stdlib=libc++ -L/opt/local/lib -L/opt/llvm/llvm_build/lib -I/opt/llvm/llvm_build/tools/clang/include -I/opt/llvm/llvm_build/include -I/opt/llvm/llvm/tools/clang/include -I/opt/llvm/llvm/include -dynamiclib -Wl,-headerpad_max_install_names -lclang -lclangFrontend -lclangAST -lclangAnalysis -lclangBasic -lclangCodeGen -lclangDriver -lclangFrontendTool -lclangLex -lclangParse -lclangSema -lclangEdit -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lLLVMX86CodeGen -lLLVMX86AsmParser -lLLVMX86Disassembler -lLLVMExecutionEngine -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMX86AsmPrinter -lLLVMX86Info -lLLVMMCParser -lLLVMCodeGen -lLLVMX86Utils -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMMC -lLLVMSupport -lLLVMBitReader -lLLVMOption -lLLVMProfileData -lpthread -lcurses -lz -lstdc++ -fPIC -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall -Wno-unused-parameter -Wwrite-strings -fno-rtti -fPIC your-clang-plugin-source.cpp -o your-clang-plugin-name.dylib

使用clang编译oc文件(载入生成的your-clang-plugin-name.dylib):

1
/opt/llvm/llvm_build/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 -mios-version-min=8.0 -Xclang -load -Xclang ~/Desktop/your-clang-plugin-name.dylib -Xclang -add-plugin -Xclang your-clang-plugin-name -c ./ocClsDemo.m

Xcode集成Clang插件:
要在Xcode中使用Clang插件,需要Hack Xcode.app。
XcodeHacking.zip
通过command-line方式或者手动拷贝的方式将XcodeHack拷贝到Xcode目录中,如果Specifications文件不存在则手动创建一个。

1
2
sudo mv HackedClang.xcplugin `xcode-select -print- path`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec `xcode-select -print- path`/../Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications



之后在Xcode->Target-Build Settings->Build Options->Compiler for C/C++/Objective-C选择Clang LLVM Trunk即可使得Xcode使用1生成的的Clang来编译。至于其他的命令行参数,均可以通过Xcode设置完成。

3、clang插件在Xcode工程的配置:
clang插件示例工程
a、首先通过Xcode将自定义clang插件编译成动态库(dylib)。


b、其次编译生成分析工具XcodeCodeAnalyser


c、设置工程
使用用户编译的Clang载入ClangPlugin去编译并生成各种中间文件。编译完成之后使用Xcode提供的post_build_action_shell机制调用分析工具,生成最终结果。


参考:
http://railsware.com/blog/2014/02/28/creation-and-using-clang-plugin-with-xcode/
http://blog.mrriddler.com/2017/02/24/Clang插件-Sherlock/
https://lmsgsendnilself.github.io/blog/2017/02/28/clangtojszi-yuan/
http://kangwang1988.github.io/tech/2016/11/01/validate-ios-api-using-clang-plugin.html
https://github.com/kangwang1988/XcodeValidAPI
http://www.cocoachina.com/ios/20151211/14562.html
http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112096&idx=1&sn=ce8fccce7d5f70e30c078e63e8ea0d15&scene=21#wechat_redirect

三味猪屋

代码统计小贴示

发表于 2017-06-01 | 分类于 cloc

cloc-1.64.pl
CLOC源码地址
用法一:直接执行perl脚本,如图:

用法二:通过brew安装cloc,使用命令行方式,如图:

三味猪屋

iOS包大小瘦身方案(一)- iOS安装包构成

发表于 2017-06-01 | 分类于 app-thinning

要想将iOS安装包瘦身,首先得熟悉iOS安装包的构成,然后根据各个部分逐个优化。
1、iOS安装包构成:
iOS安装包是以后缀名为“ipa”一个压缩包,该压缩包内容大致包含三块内容:可执行文件、资源文件、签名文件
构成如下图:

2、可执行文件构成:
可执行文件是通过编译器、连接器将我们编写的代码、静态库、动态库编译成的文件,大致可分为代码段和数据段
构成如下:

3、可执行文件支持的cpu架构以及安装包大小关系
以“滴滴打车”为例,对比去年3.9.7版本以及最新4.3.10版本
3.9.7版本:



4.3.10版本:



由此可见支持的CPU体系架构越多,对安装包大小成正比例影响。
4、可执行文件各段所占大小
64位架构:



32位架构:



5、第三方SDK大小占比






通过比较不难看出第三方SDK编译出来的可执行文件占比达到62.5%
6、资源文件对安装包的影响
未对图片资源进行无损压缩之后包大小

对图片资源进行无损压缩之后包大小

下图为对我们app中图片资源进行无损压缩的效果:

Tips:
1、查看可执行文件大小:



linkmap.js
2、MachOView使用:
MachOView下载地址
MachOView源码地址
3、ImageOptim使用:
ImageOptim下载地址
4、去除工程中多余的图片资源:
a、脚本
详见链接

1
2
3
4
5
6
7
8
9
#!/bin/sh
PROJ=`find . -name '*.xib' -o -name '*.[mh]'`
for png in `find . -name '*.png'`
do
name=`basename $png`
if ! grep -qhs "$name" "$PROJ"; then
echo "$png is not referenced"
fi
done

b、Unused
Unused
c、LSUnusedResources
LSUnusedResources

三味猪屋

深入理解"weak-strong dance"

发表于 2017-05-31 | 分类于 weak-strong dance

使用block时利用”weak-strong dance”解决循环引用大家都知道,但这里有三点值得注意:
1、当self指向的对象已经被废弃的情况下,持有block成员变量也不存在了,这个时候block对象应该已经没有被变量所持有了,它的引用计数应该已经为0了,它应该被废弃了啊,为什么它还能继续存在并执行。
如以下代码,在 block 执行前退出这个页面的话,该 Controller 实例会被废弃,但 Block 还是会执行,会打印“self is (null)”。

1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.handler = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"self is %@", strongSelf);
};
NSTimeInterval interval = 6.0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), weakSelf.handler);
}

理解这个问题需要了解block原理,简单一点的说,block有三种类型:
NSConcreteStackBlock(栈)
NSConcreteGlobalBlock(全局)
NSConcreteMallocBlock(堆)

如何实现这三种类型的block:

使用clang -rewrite-objc $filePath将OC代码转化为C++代码实现。如何安装编译clang


如果 block 在记述全局变量的地方被设置或者 block 没有捕获外部变量,那就生成一个 NSConcreteGlobalBlock 实例。其它情况都会生成一个 NSConcreteStackBlock 实例,也就是说,它是在栈上的,所以一旦它所属的变量超出了变量作用域,该 block 就被废弃了。而当发生以下任一情况时:
a、手动调用 block 的实例方法copy。
b、block 作为函数返回值返回。
c、将 block 赋值给附有__strong修饰符的成员变量。
d、在方法名中含有usingBlock的 Cocoa 框架方法或 GCD 的 API 中传递 block。
所以NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现。
看一下Block_copy()实现:

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
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}

综上所述,已经可以解答问题1。把 block 赋值给self.handler的时候,在栈上生成的 block 被复制了一份放到堆上。而之后如果你把这个 block 当作 GCD 参数使用,GCD 函数内部会把该 block 再 copy 一遍,而此时 block 已经在堆上,则该 block 的引用计数加1。所以此时 block 的引用计数是大于1的,即使self对象被废弃 block 会被 release 一次,但它的引用计数仍然大于0,故而不会被废弃。
Tips:
其实在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。

更多请查看苹果官方文档

2、在 block 内部使用weakSelf就是为了让 block 对象不持有self指向的对象,那在 block 内部又把weakSelf赋给strongSelf不就又持有self对象了么?又循环引用了?
理解这个问题就需要了解block捕获对象变量。
使用weak修饰的变量时weak是不会持有对象,它用一张 weak 表来管理对象和变量。赋值的时候它会以赋值对象的地址作为 key,变量的地址为 value,注册到 weak 表中。一旦该对象被废弃,就通过对象地址在 weak 表中找到变量的地址,赋值为 nil,然后将该条记录从 weak 表中删除。
那使用 “weak-strong dance” 的时候是怎么个情况呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __xx_block_impl_y {
struct __block_impl impl;
__weak OCClass *occlass;
// ...
};
static void __xx_block_func_y(struct __xx_block_impl_y *__cself) {
OCClass *occlass = __cself -> occlass;
// ...
}

每次使用weak变量的时候,都会取出该变量指向的对象并 retain,然后将该对象注册到 autoreleasepool 中。通过上述代码我们可以发现,在xx_block_func_y中,局部变量occlass会持有捕获的对象,然后对象会被注册到 autoreleasepool。这是延长对象生命周期的关键(保证在执行 Block 期间对象不会被废弃),但这不会造成循环引用,当函数执行结束,变量occlass超出作用域,过一会儿(一般一次 RunLoop 之后),对象就被释放了。所以 weak-strong dance 的行为非常符合预期:延长捕获对象的生命周期,一旦 Block 执行完,对象被释放,而 Block 也会被释放(如果被 GCD 之类的 API copy 过一次增加了引用计数,那最终也会被 GCD 释放)。

3、使用”weak-strong dance”一定安全吗?
其实通过问题2就可以理解,”weak-strong dance”并不是安全的,”weak-strong dance”只实用与block已经捕获到对象的情况,”weak-strong dance”并不能保证 block 所引用对象的释放时机在执行之后, 更安全的做法应该是在 block 内部使用 strongSelf 时进行 nil检测,这样可以避免上述情况。

参考:
https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
http://albertodebortoli.com/blog/2013/08/03/objective-c-blocks-caveat/
http://www.jianshu.com/p/737999a30544

三味猪屋

clang源码编译安装

发表于 2017-05-30 | 分类于 clang

环境配置:
1、配置Git
2、安装cmake编译环境

下载代码:

1
2
3
4
5
6
7
8
9
10
cd /opt
sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`
git clone -b release_39 git@github.com:llvm-mirror/llvm.git llvm
git clone -b release_39 git@github.com:llvm-mirror/clang.git llvm/tools/clang
git clone -b release_39 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b release_39 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt

编译代码:

1
2
3
4
mkdir llvm_build
cd llvm_build
cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release
make -j`sysctl -n hw.logicalcpu`




结果:

三味猪屋

Mac配置Git

发表于 2017-05-27 | 分类于 iOS

安装Git:
与大多数软件在Mac上安装一样,安装方式基本上都提供三种方式:
1、通过pkg或者dmg安装。
http://sourceforge.net/projects/git-osx-installer/
2、通过MacPorts安装。
$ sudo port install git-core +svn +doc +bash_completion +gitweb
3、通过homebrew安装。
brew install git

设置Git账户及邮箱:
1、设置全局账户及邮箱

1
2
$ git config --global user.name luohs
$ git config --global user.email luohs@example.com

修改配置后,可以查看本地git配置文件:

1
2
3
4
$ cat ~/.gitconfig
[user]
name = luohs
email = luohs@example.com


2、取消全局配置

1
2
$ git config --global --unset user.name
$ git config --global --unset user.email

创建本地SSH:
查看本地是否已经创建rsa,如果存在id_rsa和id_rsa.pub说明已经创建
$ ls ~/.ssh

终端输入ssh-keygen -t rsa -C "luohs@example.com"
“~/.ssh”不存在id_rsa和id_rsa.pub时:

“~/.ssh”已存在id_rsa和id_rsa.pub时:

路径选择 : 使用该命令之后, 会出现提示选择ssh-key生成路径, 这里直接点回车默认即可, 生成的ssh-key在默认路径中(~/.ssh)。
密码确认 : 这里我们不使用密码进行登录, 用密码太麻烦;就一路回车下去。

SSH配置到GitHub:
登录到GitHub,进入setting,选中SSH:

验证:
ssh -T git@github.com

参考:
http://gitref.org/zh/index.html
https://git-scm.com/book/zh/v1/起步

三味猪屋

Why shouldn't use accessors in init/dealloc?

发表于 2017-05-22 | 分类于 iOS


其实也并不是一刀切,所有的accessor都不能在init和dealloc中使用,只有同时满足以下两个条件时才会有问题:
第一:父类init/dealloc使用setter。
第二:子类重写了父类的setter。
如果父类的init中调用了某个属性的setter方法,当通过self=[super init]对子类进行初始化的时候,此时会优先调用调用父类的init方法,当执行父类的init方法时,父类init方法调用了某个属性的setter方法,由于面向对象的多态特性就会调用子类重写了父类的那个setter方法,但是此时子类还未初始化,所有就会出现问题。
同理,当父类进行dealloc时,此时会优先调用子类的dealloc方法,如果父类在dealloc时调用了setter并且该setter被子类重写,同样由于面向对象的多态性就会调用到子类的setter。而此时子类的dealloc已经被调用了,那么再执行子类的代码就会出现问题。

参考:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW5

三味猪屋

如何设置导航栏返回按钮的事件

发表于 2017-05-19 | 分类于 iOS

说到设置导航栏返回按钮事件,首先想到是设置self.navigationItem.leftBarButtonItem。但是leftBarButtonItem与系统自带的backBarButtonItem在样式和frame是不一样的,特别是APP如果没有从整个框架层去统一导航栏的返回按钮,就会出现有的页面是系统自带的backBarButtonItem,有的页面是程序员设置的leftBarButtonItem,导致界面无一致性。
其实可以通过UINavigationBarDelegate提供的方法来设置我们自定义事件。

我们可以为UINavigatonController创建一个类别(Category)来定制navigationBar: shouldPopItem:的逻辑。由于UINavigatonController自带的 UINavigationBar的delegate就是UINavigatonController本身,所以在创建导航栏的实例时不需要手动去设置delegate。
代码如下:

UIViewController-BackButtonHandler实现如下:

三味猪屋

UIView中使用KVO的正确使用姿势

发表于 2017-05-18 | 分类于 iOS

相信大家对于KVO的使用应该不会存在什么疑问,addObserver:forKeyPath:options:context:与removeObserver:forKeyPath:context:必须成对出现。

按照官方文档中描述,Observer(观察者)在被释放时是不会自动移除的,被观察的对象会继续发送通知而是不会去检查 Observer(观察者)的状态的,当发送给一个被释放的 Observer(观察者)时内存就会出现异常。所以由此可以推断 Observer(观察者)在调用addObserver:forKeyPath:options:context:注册时不是被强引用的。
按照官方文档中描述,Observer(观察者)通常情况下是在init或者viewDidLoad时注册,在dealloc时移除。
所以在UIView我们会经常在init进行注册,在dealloc时进行注册。这本身没有问题,因为苹果官方文档也这么说的,但是总是感觉有点不好,能不能像UIViewController那样,通过生命周期的相关接口进行注册和取消呢?
负责UIView的生命周期的接口有:

UIView生命周期如下:
未添加subView:
push:willMoveToSuperview->didMoveToSuperview->willMoveToWindow->didMoveToWindow->layoutSubviews
pop:->willMoveToWindow->didMoveToWindow->willMoveToSuperview->didMoveToSuperview->removeFromSuperview->dealloc
添加subView:
push:willMoveToSuperview->didMoveToSuperview->willMoveToWindow->didMoveToWindow->layoutSubviews->didAddSubview->layoutSubviews
pop:->willMoveToWindow->didMoveToWindow->willMoveToSuperview->didMoveToSuperview->removeFromSuperview->dealloc->willRemoveSubview
如果在UIView中实现subView的KVO,那么可以在didAddSubview中注册,在willRemoveSubview中取消。
如果在UIView中实现自己的KVO,找到眼瞎也没有找到removeFromSuperview等字眼,真是失望透顶,但是大家有没有发现,在UIView生命周期中MoveToSuperview、MoveToWindow在push和pop都调用了,其中的奥秘就在参数:superview,当push时,是将UIView添加到superview,所以参数superview是不为nil的。当pop时,是将UIView从superview中移除,所以参数superview是nil。
正确的姿势:

参考:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/Articles/KVOBasics.html#//apple_ref/doc/uid/20002252-178612

123
花生-sniper

花生-sniper

25 日志
14 分类
21 标签
© 2017 花生-sniper
由 Hexo 强力驱动
主题 - NexT.Pisces