豹哥嵌入式讲堂:ARM Cortex-M开发之文件详解(6)- 可执行文件(.out/.elf)

9. “以类族情势”隐藏实现细节

“类族”是一种很有种的格局,可以隐蔽“抽象基类”背后的兑现细节。OC的系统框架中普遍运用此形式,比如有一个处理雇员的类,每个雇员都有“名字”和“薪水”这多少个特性,管理者可以命令其推行通常工作,不过各类雇员的行事内容却不同,主任在前导雇员做项目时,无需关系每个人如何完成其现实做事,仅需提示其动工就行。大家重构四个子类,把各样人完成具体做事的艺术,在子类实现。

率先定义一个抽象基类:

typedef NS_ENUM(NSUInteger, EOCEmployeeType){
    EOCEmployeeTypeDeveloper,
    EOCEmployeeTypeDesigner,
    EOCEmployeeTypeFinance     
}

@interface EOCEmployee : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger salary;

// 创建一个雇员对象
+(EOCEmployee*)employeeWithType:(EOCEmployeeType)type;

// 让雇员工作
- (void)doADaysWork;

@implementation EOCEmployee

+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type{
    switch (type){
          case EOCEmployeeTypeDeveloper:
                  return [EOCEmployeeTypeDeveloper new];
                  break;
          case EOCEmployeeTypeDeveloper:
                  return [EOCEmployeeTypeDesigner new];
                  break;
           case EOCEmployeeTypeDeveloper:
                  return [EOCEmployeeTypeFinance new];
                  break;
    }  
}

- (void)doADayWork{
  // 子类去实现
}

@end

 然后,每个“实体子类”都从基类继承而来,例如:

@interface EOCEmployeeDeveloper : EOCEmployee

@end

@implementation EOCEmployeeDeveloper

- (void)doADaysWork{
   [self wirteCode];
}

@end

 在本例中,基类实现了一个“类模式”,该模式遵照待创制的雇员系列分配好相应的雇员类实例,这种“工厂格局”是开创类族的艺术之一。

即便目的所属的类位居某个类族中,你或许认为自己创办了某个类的实例,但是事实上创设的却是其子类的实例。

OC中的NSNumber、NSArray等都是类族。

  • 类族情势可以把实现细节隐藏在一套简单的国有接口前边。
  • 系统框架中时时应用类族
  • 从类族的集体抽象基类中集成子类时要当心,若有付出文档,应先阅读。
2.1.3 readelf.exe用法

  readelf.exe坚守标准的windows命令行用法,使用–help可以列出所有命令option及其简介,下边仅列出相比常用的option。

C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe --help
Usage: readelf <option(s)> elf-file(s)
 Display information about the contents of ELF format files
 Options are:
  -a --all               Equivalent to: -h -l -S -s -r -d -V -A -I
  -h --file-header       Display the ELF file header
  -l --program-headers   Display the program headers
     --segments          An alias for --program-headers
  -S --section-headers   Display the sections' header
     --sections          An alias for --section-headers
  -t --section-details   Display the section details
  -e --headers           Equivalent to: -h -l -S
  -s --syms              Display the symbol table
     --symbols           An alias for --syms
  --dyn-syms             Display the dynamic symbol table
  -r --relocs            Display the relocations (if present)
  -d --dynamic           Display the dynamic section (if present)
  -V --version-info      Display the version sections (if present)
  -A --arch-specific     Display architecture specific information (if any)
  -I --histogram         Display histogram of bucket list lengths
  @<file>                Read options from <file>

24. 将类的贯彻代码分散到便于管理的数个分类之中

  • 使用分类机制把类的兑现代码划分成易于管理的小块
  • 将相应算得”私有“的法子归入名叫Private的归类中,隐藏实现细节。

  我们好,我是豹哥,猎豹的豹,犀利哥的哥。前些天豹哥给我们讲的是嵌入式开发里的executable文件(elf)

27. 使用 “class-continuation分类”隐藏实现细节

“class-continuation分类”和普通的分类不同,它必须定义在其所接续的不行累的贯彻文件里。其首要之处在于,这是绝无仅有可以阐明实例变量的归类,而且此分类没有特定的实现公文,其中的措施都应当定义在类的主实现文件里。而且,和此外分类不同,它并未名字,比如:

@interface Person ()
// Methods here
@end
  •  通过“class-continuation分类”向类中新增实例变量
  • 一经某属性在主接口中表明为只读,而类的中间又要用设置形式修改此属性,那么就在“class-continuation分类”旅长其扩充为“可读写”
  • 把个人方法的原型讲明在“class-continuation分类”里面
  • 若想让类所遵守的协商不为人所知,则可于“class-continuation分类”中声称。

32. 以弱引用避免循环引用

倘使六个对象,相互引用,那么这两个目的都心有余而力不足被放出,爆发内存泄露。

unsafe_unretained 和 weak的区别:

当指向某个实例的引用移除后,unsafe_unretained属性仍指向卓殊已经回收的实例,而weak属性则针对nil。weak比unsafe_unretained应用可以令代码更安全。

  • 当一些引用设为weak,可防止出现循环引用
  • weak引用可以自行清空,也足以不自行清空。

2.3 elf文件layout

  经过上一节对demo.elf里相继header的解析,此时大家便可以粗略地画出elf文件layout。

File offset Data content Data size in bytes
0x00000000 ELF file header 52
0x00000034 Image binary (Section4-A0 rw, .intvec中断向量表) 0x40
0x00000074 Image binary (Section5-P1 ro, readonly section(.text, .rodata…)) 0x484
0x000004F8 Section8-20 (包含各种辅助调试和系统段.debug_xx, .iar.xx) 0x5E3E
0x00006336 NULL 0x2
0x00006338 Section1-.shstrtab字符串表 0xE6
0x00006420 Section2-.strtab字符串信息 0xB7C
0x00006F9C Section3-.symtab符号信息 0xC60
0x00007BFC ELF Program header 0x20
0x00007C1C ELF Section headers (0 – 20) 21 * 40

25. 连接为第三方类的分类名称加前缀

比如说你想给系统类添加个格局,倘诺您从未添加前缀的话,可能会覆盖其方法。

  • 向第三方类中添加分类时,总应给其名目加上你专用的前缀。
  • 给内部的不二法门名加上你专用的前缀。

二、解析elf文件

  所谓工欲善其事,必先利其器,在开班解析elf文件在此以前,大家亟须先找到一款适合的分析工具,readelf就是GNU/Linux官方推出的专用解析工具。有了这么些分析工具,我们便可以渐渐分析elf文件。

21. 明白OC错误模型

  • 除非爆发了可使整个应用程序崩溃的严重错误时,才使用特别。
  • 在错误不严重的事态下,使用NSError

工具1:GNU工具objcopy

位置:C:\cygwin64\bin>x86_64-w64-mingw32-objcopy.exe
用法:
      objcopy.exe -O binary -S demo.elf demo.bin
      objcopy.exe -O srec   -S demo.elf demo.s19

备注:一说需用arm-linux-objcopy,待验证

35. 利用block降低代码分散程度

  • 在创立对象时,可以运用内联的handler代码块将相关事情逻辑表明
  • 例如网络请求一般接纳代码块来回调数据

 

2.1.2 cygwin(windows下使用GNU)

  Cygwin是一个在windows平台上运行的类UNIX模拟条件,是cygnus
solutions集团(已被Redhat收购)开发的自由软件。它对于学习UNIX/Linux操作环境,或者从UNIX到Windows的应用程序移植,尤其是运用GNU工具集在Windows上展开嵌入式系统开发,相当有效。

// 下载链接
Installer:http://cygwin.com/install.html
Package:  https://cygwin.com/packages/package_list.html
// 相关包(根据平台选择)
binutils                - GNU assembler, linker, and similar utilities
cygwin32-binutils       - Binutils for Cygwin 32bit toolchain
mingw64-x86_64-binutils - Binutils for MinGW-w64 Win64 toolchain 
mingw64-i686-binutils   - Binutils for MinGW-w64 Win32 toolchain

  下载安装好cygwin包后,便可在设置目录下\cygwin64\bin\找到x86_64-w64-mingw32-readelf.exe工具(豹哥选取的是mingw64-x86_64-binutils包)。

28. 通过协商提供匿名对象

如下面的代码:

@property (nonatomic, weak) id <WCEDelegate> delegate;

由于该属性的花色id<EOCDelegate>,所以实际上任何类的靶子都能充当这一性能,对于持有此属性的类来说,delegate就是”匿名的“。

  • 共谋可在某种程度上提供匿名类型。具体的目的类型可以淡化成坚守某探讨的id类型,协议里规定了对象所应实现的方法
  • 利用匿名对象来隐藏类型名称
  • 如过具体品种不首要,首要的是目的可以响应(定义在协议里的)特定措施,那么可接纳匿名对象来表示。
2.2.1 获得file header
C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -h demo.elf
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x41
  Start of program headers:          31740 (bytes into file)
  Start of section headers:          31772 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           40 (bytes)
  Number of section headers:         21
  Section header string table index: 1

  第一步首先分析file header,前边介绍里说过file
header是放在文件最前面的。通过readelf -h命令可以获取file
header解析后的信息。让大家来相比较一下,使用HexEditor直接打开demo.elf可拿到如下数据,仅取前52bytes(0x34)数据,因为Elf32_Ehdr大小就是52bytes:

offset(h)
00000000: 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00
00000010: 02 00 28 00 01 00 00 00 41 00 00 00 FC 7B 00 00
00000020: 1C 7C 00 00 00 00 00 05 34 00 20 00 01 00 28 00
00000030: 15 00 01 00 -- -- -- -- -- -- -- -- -- -- -- --

  可以见见前16byte是e_ident[16],与分析后的Magic是一致的;再来验证prgram
header偏移e_phoff=0x00007BFC,数量e_phnum=0x0001,大小e_phentsize=0x0020,也是与分析后的音讯匹配的;余下可自动对照。

15. 用前缀制止命名空间顶牛

应当为持有的名号都助长适当的前缀,比如,你所在的小卖部宿州Effective
Widgets,那么就可以在公共部分代码中使用EWS做前缀,倘使略微代码只用于Effective
Browser的浏览器项目中,能够利用EWB作前缀。

前缀最好是三个假名的,因为Apple宣称其保存使用所有“两字母前缀”。

  • 挑选与您的商号,应用程序或双边皆有涉嫌之名称作为类名的前缀,并在具有代码中采纳这一前缀
  • 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀。

2.1 解析工具readelf

  既然elf文件是Linux系统下常用的可执行文件格式,那么Linux社区一定会有配套的工具去分析它,是的,这一个工具就叫readelf,在GNU工具集binutils里。

34. 为常用的block类型创设typedef

比如:

typedef void(^WCECompletionHander)(NSData *data);
  •  用typedef重新定义块类型,可让块变量用起来更为简便易行
  • 定义新品种时,应按照命名规则

一、elf文件基础

  ELF全称Executable and Linkable
Format,可实施连接格式,ELF格式的公文最早用于存储Linux程序,后演化到ARM系统上存储ARM程序。ELF文件(目的文件)格式紧要二种:

  • 可重定向文件:用来和其余的对象文件一起来创制一个可执行文件或者共享目的文件(也称object文件或者静态库文件,通常后缀为.o和.a的文件)。这么些文件是用于编译和链接阶段。
  • 可执行文件:用于转移应用image,载入存储器执行(后缀日常为.out或者.elf)。这多少个文件是用以加载执行阶段。
  • 共享目的文件:用于和任何共享目标文件或者object文件一起生成可执行文件,或者和可执行文件一起创制应用image。(也称共享库文件,后缀为.so的文本)。这个文件既可用以编译和链接阶段,也可用以加载执行等级。

  我们在ARM开发中更多接触的是前二种格式,第一种格式前面体系作品relocatable文件早就介绍过,本文的骨干是第两种格式-可执行文件。不管是哪一种格式的ELF文件,其都可能含有如下两种基本索引表:

  • file header:一般在文书的起头,描述了ELF文件的完好协会情状。
  • program
    header
    :告诉系统怎么样创立image,可执行文件必须具备program
    header,而可重定向文件则不需要。
  • section
    header
    :包含了描述文件section的音讯,每个section都有一个header,每一个header给出诸如section名称、section大小等信息。可重定向文件必须含有section
    header。

  既然知道了设有二种索引表,那么表的结构定义在何地吗?github上的linux仓库里有切实可行定义,在elf.h头文件里。

Linux仓库:https://github.com/torvalds/linux.git
elf.h路径:\linux\include\uapi\linux\elf.h

  打开elf.h文件便可找到五个表的原型定义,鉴于目前的ARM
Cortex-M都是32bit,所以这里仅列出32bit下的表的原型:Elf32_Ehdr、Elf32_Phdr、Elf32_Shdr。

// file header
#define EI_NIDENT    16
typedef struct elf32_hdr{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */  
  Elf32_Half    e_machine;              /* Architecture */  
  Elf32_Word    e_version;              /* Object file version */  
  Elf32_Addr    e_entry;                /* Entry point virtual address */  
  Elf32_Off     e_phoff;                /* Program header table file offset */  
  Elf32_Off     e_shoff;                /* Section header table file offset */  
  Elf32_Word    e_flags;                /* Processor-specific flags */  
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */  
  Elf32_Half    e_phentsize;            /* Program header table entry size */  
  Elf32_Half    e_phnum;                /* Program header table entry count */  
  Elf32_Half    e_shentsize;            /* Section header table entry size */  
  Elf32_Half    e_shnum;                /* Section header table entry count */  
  Elf32_Half    e_shstrndx;             /* Section header string table index */ 
} Elf32_Ehdr;

// program header
typedef struct elf32_phdr{
  Elf32_Word    p_type;           /* Segment type */
  Elf32_Off     p_offset;         /* Segment file offset */
  Elf32_Addr    p_vaddr;          /* Segment virtual address */
  Elf32_Addr    p_paddr;          /* Segment physical address */
  Elf32_Word    p_filesz;         /* Segment size in file */
  Elf32_Word    p_memsz;          /* Segment size in memory */
  Elf32_Word    p_flags;          /* Segment flags */
  Elf32_Word    p_align;          /* Segment alignment, file & memory */
} Elf32_Phdr;

// section header
typedef struct elf32_shdr {
  Elf32_Word    sh_name;          /* Section name, index in string tbl */
  Elf32_Word    sh_type;          /* Type of section */
  Elf32_Word    sh_flags;         /* Miscellaneous section attributes */
  Elf32_Addr    sh_addr;          /* Section virtual addr at execution */
  Elf32_Off     sh_offset;        /* Section file offset */
  Elf32_Word    sh_size;          /* Size of section in bytes */
  Elf32_Word    sh_link;          /* Index of another section */
  Elf32_Word    sh_info;          /* Additional section information */
  Elf32_Word    sh_addralign;     /* Section alignment */
  Elf32_Word    sh_entsize;       /* Entry size if section holds table */
} Elf32_Shdr;

18. 尽量使用不可变对象

设计类的时候,应充足运用属性来封装数据,尽量把对外宣布出来的性能设为只读,而且只在确有必要时才将属性对外发布。

  • 尽可能创立不可变的靶子
  • 若某属性仅可于对象内部修改,则在.m文件中,则将其由readonly变成readwrite属性。
  • 不用把可变的collection作为性能公开,而应提供相关措施,以此修改对象中的collection
2.1.1 GNU工具集(binutils)

  GNU是“GNU’s Not
Unix”的递归缩写,又叫做GNU计划,很多著名的开源软件及工具都是GNU开发的(比如出名的C语言编译器GCC)。binutils是GNU一体系binary小工具的集结,大家从上面的链接里找到官方binutils包。

主页:http://www.gnu.org/software/binutils/
仓库:git://sourceware.org/git/binutils-gdb.git
下载:http://ftp.gnu.org/gnu/binutils/
文档:https://sourceware.org/binutils/docs-2.29/binutils/index.html

  不过利用上述包里的readelf会有一个问题,上述工具是在Linux系统下利用的,而大家通常做ARM
Cortex-M开发很多都是在windows平台下,那么怎么在windows下行使readelf工具呢?别急,cygwin给了俺们帮助。

12. 知情音讯转发机制

当目标收取到不能解读的音信后,就会启动“音讯转发”机制,程序员可经由此经过告诉对象应该怎么着处理未知信息。

假若在控制马尔默看到 unrecognized selector sent to instance 0x87
就认证你曾向某个对象发送过一条其不可能解读的音信,从而启动了消息转发机制,然后以程序崩溃而终止。

信息转发分为多少个等级:

  1. 征得接收者,所属的类,看其是否能动态增长方法,以处理当下以此“未知的选用子(unknown
    selector)”,这叫做“动态方法分析”
  2. 第二等级,涉及“完整的信息转发机制”,即便运行期系统已经把第一阶段实施完了,那么接收者自己就不能再以动态新增方法的一手来响应包含该选取子的信息了。此时,运行期系统会请求接收者以此外手段来拍卖与信息相关的法子调用。那又分为两小步:
    1. 率先,看接收者看看有没有另外对象是否处理这条音信
    2. 万一有,则运行期系统会把新闻转给这个指标,于是转发郭恒截止,即使没有“备用的收信人”,则启动全体的信息转发机制,运行期系统会把与音信有关的成套细节都打包到NSInvocation对象中,再给接收者最终一次机遇,令其想法解决方今还未处理的这条音信。

动态方法分析:

对象在吸纳不可能解读的音信后,首先将调用其所属类的下列类措施:

// 如果该类调用了一个没有实现的实例方法,会调用此方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
// 如果该类调用了一个没有实现的类方法,会调用此方法
+ (BOOL)resolveClassMethod;

 该办法的参数就是这个未知的选取子,其重临值为Boolean类型,表示这一个类是否能增产一个实例方法用以处理此选用子。在延续往下实施转发机制在此以前,我们得以行使runtime动态的增添这些措施。

使用这种模式的前提是:相关办法的兑现代码已经写好,只等着运行的时候动态插在类里面就足以了。

备用接收者:

眼下接收者还有第二次机遇能处理未知的采用子,在这一步中,运行期系统会问它:能无法把这条信息转给其他接收者来处理:

// 方法参数代表未知的选择子,若当前接收者能够找到备援对象,则将其返回,如果找不到就返回nil。
- (id)forwardingTargetForSelector:(SEL)selector;

 我们得以用“组合”来模拟出“多重继承”的一些特征,在一个目的内部,可能还有任何一多元对象,该对象可经通过方法将可以处理某选用子的相干内部对象回来,这样的话,在外界看来,好像是由该目标亲自处理的。

一体化的信息转发:

假诺转正已经赶到这一步的话,那么唯一能做的就是启用完整的音讯转发机制了,系统会创建NSInvocation
对象,把与没有处理的这条音讯有关的万事细节都卷入于其中,此目的涵盖选拔子、目标(target)及参数,在触发NSInvocation对象时,“音信派发系统”将亲自出马,把音讯指派给目的对象。

此步骤会调用下列情势来转发新闻:

// 该方法可以实现的很简单,只需要改变调用目标,是消息在新目标上得以调用即可,然而这样实现出来的方法与“备援接收者”方案所实现的方法等效,所以很少有人采用这么简单的实现方法,比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改换选择子等等。
- (void)forwardInvocation:(NSInvocation *)invocation;

 实现此格局时,若发现某调用操作不应由本类处理,则需要调用超类的同名方法。这样的话,继承系列中的每个类都有机遇处理此调用请求,直到NSObject,假设最后调用了NSObject类的艺术,那么该格局还会继续调用“doesNotRecognizeSelector”,以抛出分外,此充足声明选取子最后未能获取处理。

音信转发全流程:

图片 1

图片 2

收信人在每一步中均有机遇处理音信,步骤越未来,处理音讯的代价就越大,最好能在率先步处理完,这样的话,运行期系统就可以将此方法缓存起来了,倘诺那么些类的实例稍后还收受同名采取子,那么根本无需启动音讯转发流程。假若想在第三步里把音讯转给备援的收信人,这还不如把转发操作提前到第二步。因为第三步只是修改了调用目的,那项改动放在第二部执行会进一步简易,不然的话,还得创立并处理一体化的NSInvocation。

  • 若对象不能响应某个采取子,则跻身信息转发流程。
  • 经过运行期的动态方法分析效能,大家可以在需要采取某个方法时再将其插手类中。
  • 对象可以把其无法解读的某些选用子转交给任何对象来拍卖
  • 透过上述两步之后,假设依然不曾艺术处理接纳子,这就开行全体的消息转发机制。

http://www.cocoachina.com/ios/20150604/12013.html 相关的事例

2.2 渐渐分析elf文件

  万事俱备了,伊始分析elf文件,以第三节课project文件里demo工程为例。编译链接该工程可在D:\myProject\bsp\builds\demo\Release\Exe路径下得到demo.elf文件。该文件大小32612
bytes,分明这样简单的一个小工程image
size不容许这么大,表达elf文件里的笔录消息数量占比非凡大。

23. 透过委托与数据源协议举办对象间通信

寄托格局:定义一套接口,某目标若想接受另一个目标的委托,则需要实现那多少个接口,以便成为其”委托对象”,而这”另一个对象“则可以给其委托对象回传一些信息,也得以在发生相关事件时通报委托对象。

  • 信托格局为对象提供了一套接口,使其可由此将相关事件告诉其他对象
  • 将委托对象应该扶助的接口定义成协议,在协和中把可能需要处理的风波定义成方法
  • 当某对象需要从其余一个目的中获取数据时,可以拔取委托形式,比如
    tableView的dataSource
  • 只要有必不可少,可实现含有位段的结构体,将委托对象是不是能响应相关协商章程这一信息缓存下来,比如,注明一个属性,记录是否落实了某个方法。

  文件涉及:linker文件

14. 理解“类对象”的用意

对象类型并非在编译期就绑定好了,而是要在运行期查找。而且,还有个特其余类叫做id,它能替代任意的OC对象类型,一般景色下,应该指明信息接收者的求实项目,这样的话,假使向其发送了不可以解读的信息,那么编译器就会生出警告音信,而项目为id的对象则不然,编译器嘉定它亦可响应所有的信息。

“在运行期检视对象类型”,那几个操作也称为“类型信息查询”(内省),这些强大而有效的特性内置于Foundation框架的NSObject协议里,凡是由公共根类(common
root
class)继承而来的目的都要听从此协议。在先后中不要直接相比对象所属的类,明智的做法是调用“类型音讯查询办法”。

在此以前,我们看下OC对象的真相是何等?

各样OC对象实例都是指向某块内存数据的指针,所以在宣称变量时,类型前面要跟一个“*”字符,如下:

// pointerVariable可以理解成存放内存地址的变量,而NSString 自身的数据就存储于那个地址中,因此可以说,该变量”指向“NSString 实例。所有OC对象都是如此,
NSString *pointerVariable = @"Some string";

 描述OC对象所用的数据结构定义在运行期程序库的头文件里,id类型本身也定义在此地:

typedef struct objc_object{
    Class isa;
}*id;

 每个对象,结构体的第一个成员是Class类的变量。该变量定义了目的所属的类,平日称为“is
a”指针,例如,刚才的例证中具有的对象“是一个”(is a)NSString,所以其“is
a”指针就对准NSString。Class对象也定义在运作期程序库的头文件中:

typedef stuct objc_class *Class;
struct objc_class{
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list *methodList;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
}

 此结构体存放类的“元数据”,例如类的实例实现了多少个艺术,具备多少个实例变量等消息。此结构体的第一个变量也是isa指针,这评释Class本身亦为OC对象。结构体里还有个变量为super_class,它定义了本类的超类。类对象所属的花色(也就是isa指针所针对的档次),是此外一个类,叫做“元类”,用来表述类对象自我所独具的元数据。“类措施”就定义于此地,因为这些点子可以知道成类对象的实例方法。每个类仅有一个“类对象”,而各种“类对象”仅有一个与之有关的“元类”。

super_class 指针确立了持续关系,而isa指针描述了实例所属的类。

  • 每个实例都有一个指向Class对象的指针,用以注明其项目,而这么些Class对象则构成了类的接续连串。
  • 倘使目标类型不能再编译期确定,那么就相应运用类型音信查询艺术来刹那
  • 尽量利用类型消息查询艺术来规定目标类型,而并非直接相比类对象,因为某些对象可能实现了消息转发效用。

  第四、五节课里,豹哥已经给大家介绍了2种output文件,本文继续给我们讲project生成的另一种output文件-executable文件,也是专门首要的output文件。

29. 知情引用计数

  • 引用计数机制通过方可递增递减的计数器来治本内存。对象创建好未来,其保存计数至少为1.若保存计数为正,则对象继续存活,当保留计数将为0时,对象就销毁了
  • 在对象表明期中,此外对象通过引用来保存或自由此目的,保留和假释操作分别会递增及递减保留计数

工具2:IAR工具ielftool.exe

位置:\IAR Systems\Embedded Workbench xxx\arm\bin\ielftool.exe
用法:
      ielftool.exe --bin  demo.elf demo.bin
      ielftool.exe --ihex demo.elf demo.hex
      ielftool.exe --srec demo.elf demo.s19

  至此,嵌入式开发里的executable文件(elf)文件豹哥便介绍完毕了,掌声在哪儿~~~

3. 多用字面量语法,少用与之等价的措施

  上边是二种艺术的对待:

// 使用字面量语法的例子
NSArray *array1 = @[@"1",,@"2"];

NSNumber *number1 = @1;

NSDictionary *dictionary1 = @{@"key":@"value"};

// 使用与之对应的方法
NSArray *array2 = [NSArray arrayWithObjects:@"1",@"2",nil];

NSNumber *number2 = [NSNumber numberWithInt:2];

NSDictionary *dictionary2 = [NSDictionary dictionaryWithWithObjectsAndKeys:@"value":@"key"];
  •  使用字面量语法来成立字符串、数值、数组、字典。与健康方法相比,更加简洁
  • 应该通过取下标操作来访问数组下标或字典中的键所对应的因素
  • 选用字面量语法创设数组或字典时,若值中有nil,则会抛出异常,由此,需确保值里面不含nil
2.2.2 获得program header
C:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -l demo.elf

Elf file type is EXEC (Executable file)
Entry point 0x41
There are 1 program headers, starting at offset 31740

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000034 0x00000000 0x00000000 0x004c4 0x004c4 R E 0x100

 Section to Segment mapping:
  Segment Sections...
   00     A0 rw P1 ro

  再来分析program header,通过readelf -l命令可以赢得program
header解析后的信息。从上边可以摸清header起先地点在demo.elf的31740
byte处(与file header里的e_phoff音讯是呼应的),header音讯指示program
data从offset 0x34起来,大小共0x4c4
bytes,Reset_Handler入口是0x41。继续在HexEditor查看31740处起初的32byte数据,因为Elf32_Phdr大小就是32bytes:

offset(h)
00007BF0: -- -- -- -- -- -- -- -- -- -- -- -- 01 00 00 00
00007C00: 34 00 00 00 00 00 00 00 00 00 00 00 C4 04 00 00
00007C10: C4 04 00 00 05 00 00 00 00 01 00 00 -- -- -- --

  可以见到p_offset=0x00000034,p_memsz=0x000004c4,
与地点解析后的信息是平等的;余下可自动对照。
这里的音信就相比较首要了,因为这提醒了全部image
binary数据所在(知道了这些音信,我们便足以一向写脚本依照elf文件生成image
binary),继续在HexEditor里看下去(仅截取部分显得):

offset(h)
00000030: -- -- -- -- 00 20 00 10 41 00 00 00 03 04 00 00
00000040: 3F 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000060: 61 04 00 00 00 00 00 00 00 00 00 00 63 04 00 00
00000070: 65 04 00 00 72 B6 0E 48 0E 49 88 60 00 22 00 23
00000080: 00 24 00 25 00 26 00 27 B8 46 B9 46 BA 46 BB 46

  ARM系统的image前16个指针都是系统中断向量,咱们得以看出SP=0x10002000,
PC=0x00000041,这与地点解析的Reset_Handler入口是0x41是配合的。

33. 电动释放池

  • 活动释放池排布在栈中,对象收取autorelease信息后,系统将其放入最上方的池里
  • 客观利用自动释放池,可降低应用程序的内存峰值
  • 使用@autoreleasepool

番外一、几个elf转换image工具

  在明天的番外篇里,豹哥给我们顺便介绍几款专业的elf文件转换成image文件的工具。

17. 实现description方法

调试程序的时候,经常需要打印并查阅对象消息,我们得以重写该目的的description方法,如下:

图片 3

  • 兑现description方法再次来到一个有意义的字符串,用以描述该实例
  • 若想在调节时打印出更详实的对象描述新闻,则应促成debugDescription方法

22. 理解NSCopying协议

  • 若想让祥和所写的靶子具备拷贝功用,则需要实现NSCopying协议
  • 假定自定义的靶子分为可变和不可变,那么就要同时落实NSCopying和NSMutableCopying商谈
  • 复制对象时需控制使用浅拷贝依旧深拷贝,一般意况下进行浅拷贝

  仔细看过豹哥在此以前课程的心上人一定理解,豹哥在第四节课relocatable文件里介绍的object文件在格式上其实跟本文要讲的elf文件是近似的,它们都属于ELF文件分支。只不是relocatable文件只是高中级过渡文件,而本文要讲的elf却是标准的output文件,这一个文件几乎涵盖了工程的保有消息,有了这个文件大家既可以在线调试工程,也足以将elf文件转换成image文件,直接下载image文件数据进芯片中脱机运行。前日豹哥就为我们精心分析elf文件。

31. 在dealloc方法中只释放引用并排除监听

对象在经验其生命周期后,最后会为系统所回收,这时就要执行dealloc方法,在各类对象的生命周期内,此办法仅执行一回,也就是当保留计数为0的时候,不过具体什么日期实施,则无从保证。

在dealloc方法中,一般都是移除观测行为,注销通告。

  • 在dealloc方法里,应该做的事务就是假释指向任何对象的引用,并撤回原来订阅的”kvo“或通知大旨的等通报,不要做任何工作
  • 一经目标拥有文件讲述符等系统资源,那么相应特别编排一个措施来释放此种资源。
  • 实施异步任务的情势不应再dealloc里,只好在正规状态执行的什么方法也不应在dealloc里调用,因为此时目的已处于正在回收的意况了。
2.2.4 获得symbol list
c:cygwin64\bin>x86_64-w64-mingw32-readelf.exe -s demo.elf

Symbol table '.symtab' contains 198 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
    74: 10002018    16 OBJECT  LOCAL  DEFAULT    7 s_array
    75: 10002014     4 OBJECT  LOCAL  DEFAULT    7 s_variable0
    76: 10002010     4 OBJECT  LOCAL  DEFAULT    7 s_variable2
   135: 00000000     0 OBJECT  GLOBAL DEFAULT    4 __vector_table
   140: 00000041     0 FUNC    GLOBAL DEFAULT    5 Reset_Handler
   141: 00000098     4 OBJECT  GLOBAL DEFAULT    5 s_constant
   142: 000000ad    32 FUNC    GLOBAL DEFAULT    5 main
   143: 000000cd    14 FUNC    GLOBAL DEFAULT    5 normal_task
   144: 000000db    60 FUNC    GLOBAL DEFAULT    5 heap_task
   155: 0000034d    84 FUNC    GLOBAL DEFAULT    5 init_data_bss
   156: 000003a1    18 FUNC    GLOBAL DEFAULT    5 init_interrupts
   157: 000003dd    12 FUNC    GLOBAL DEFAULT    5 SystemInit
   186: 10002001    16 FUNC    GLOBAL DEFAULT    7 ram_task
   191: 10002034     4 OBJECT  GLOBAL DEFAULT    7 n_variable1

  通过readelf -s命令可以收获symbol
list解析后的音讯。可以见见有许三个symbol,豹哥在这里仅列出利用工程里自定义的函数和变量,从symbol表里大家得以摸清函数/变量在存储器中切实分配地址和长度,这对于我们尤其分析和调节应用是有协理的。

10. 在既有类中动用关联对象存放自定义数据

有时需要在目标中存放相关音讯,这时候我们常见都会从目的所属类中继承一个子类,然后改用那些子类对象,可是有时候类的实例可能是由某种机制所创办的,而开发者无法令这种机制制造出自己所写的子类实例。OC中有一项强大的特色可以解决,就是“关联对象”。

基于runtime来兑现,此处就不多说。

  • 可以透过“关联对象”机制来把两个目的连起来。
  • 概念关联对象时可指定内存管理语义,用以模仿定义属性时所拔取的“拥有关系”与“非用有涉嫌”
  • 除非在其他做法不可行时才应该采用关联对象,这种做法数见不鲜会引入难于查找的bug
2.2.3 获得section header
c:\cygwin64\bin>x86_64-w64-mingw32-readelf.exe -S demo.elf
There are 21 section headers, starting at offset 0x7c1c:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 006338 000000 00      0   0  4
  [ 1] .shstrtab         STRTAB          00000000 006338 0000e6 00      0   0  4
  [ 2] .strtab           STRTAB          00000000 006420 000b7c 00      0   0  4
  [ 3] .symtab           SYMTAB          00000000 006f9c 000c60 10      2 135  4
  [ 4] A0 rw             PROGBITS        00000000 000034 000040 01  AX  0   0 256
  [ 5] P1 ro             PROGBITS        00000040 000074 000484 01  AX  0   0  4
  [ 6] P3 ui             NOBITS          10000000 0004f8 002000 01  WA  0   0  8
  [ 7] P2 rw             NOBITS          10002000 0004f8 000438 01  WA  0   0  8
  [ 8] .debug_abbrev     PROGBITS        00000000 0004f8 0002c6 01      0   0  0
  [ 9] .debug_aranges    PROGBITS        00000000 0007c0 00016c 01      0   0  0
  [10] .debug_frame      PROGBITS        00000000 00092c 00057c 01      0   0  0
  [11] .debug_info       PROGBITS        00000000 000ea8 000e2e 01      0   0  0
  [12] .debug_line       PROGBITS        00000000 001cd8 000dcb 01      0   0  0
  [13] .debug_loc        PROGBITS        00000000 002aa4 00024c 01      0   0  0
  [14] .debug_macinfo    PROGBITS        00000000 002cf0 00011e 01      0   0  0
  [15] .debug_pubnames   PROGBITS        00000000 002e10 00012a 01      0   0  0
  [16] .iar.debug_frame  PROGBITS        00000000 002f3c 00007e 01      0   0  0
  [17] .iar.debug_line   PROGBITS        00000000 002fbc 000367 01      0   0  0
  [18] .comment          PROGBITS        00000000 003324 002fa2 01      0   0  0
  [19] .iar.rtmodel      PROGBITS        00000000 0062c8 000047 01      0   0  0
  [20] .ARM.attributes   ARM_ATTRIBUTES  00000000 006310 000026 01      0   0  0
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  y (purecode), p (processor specific)

  再来分析section header,通过readelf -S命令可以博得section
header解析后的音讯。可以看出有为数不少个section,其中最根本的4个section是A0(readonly
vector), P1(readonly code,data), P2(readwrite data, heap),
P3(STACK)。具体分析,各位朋友自己摸索看。

 11. 理解objc_msgSend的作用

在对象上调用方法是OC中不时采纳的功能。专业术语叫做:“传递信息”。音讯有“名称”或“采取子”,可以承受参数,而且或许还有重临值。

C语言使用“静态绑定”,在编译期就能决定运行时所应调用的函数。

OC中选取“动态绑定”,对象收取到音信之后,究竟该调用哪些方法则完全于运行期决定,甚至可以在程序运行时改变。

这边就不多解释objc_msgSend的应用,如有需要可以看runtime的利用。

objc_msgSend
函数会按照接收者和选取子的档次来调用适当的措施,为了成功此操作,该办法需要在接收者所属的类中找到其“方法列表”,假诺能找到与采纳子名称相符的格局,就跳至其实现代码,假若找不到,这就本着继承系列向上查找,尽管最后没找到,则执行“信息转发”操作。每个类都会有一块缓存,用来缓存方法,假如稍后还向该类发送与采取子相同的音讯,那么执行起来就会很快了。

  • 信息由接收者,选取子及参数构成。给某目的“发送音讯”,也就相当于在该对象上“调用方法”
  • 发给某目的的全方位信息都要由“动态信息派发系统”来拍卖,该序列会查出对应的主意,并施行其代码。

7. 在目的内部尽量间接访问实例变量

譬如,Person类有个name属性,我们在这些类的中间想取得这些name属性的数量的时候,一种是透过
self.name,一种是 _name.

这两种的区分:

  • 直白访问实例变量的进度相比较快,编译器所生成的代码会从来访问保存对象实例变量的那块内存
  • 一向访问实例变量,不会调用其“设置方法”,这就绕过了为有关属性所定义的“内存管理语义”,比如,在ARC下直接访问一个注解为copy的性能,那么并不会拷贝该属性,只会保留新值,释放旧值
  • 若是直接访问实例变量,那么不会接触“KVO”,这样做是否会暴发问题,取决于具体的靶子行为。
  • 通过属性来访问推动排查与之相关的不当,因为可以给“获取形式”或“设置方法”中新增“断点”,监控该属性的调用者及其访问时机。

注意点:

  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过性能来写
  • 在发轫化方法及dealloc方法中,总是应该直接通过实例变量来读写多少
  • 奇迹会利用惰性开端化技术配置某份数据,这种气象下,需要通过性能来读取数据

26. 不用再分类中声明属性

  • 把封装数据所用的成套属性都定义在主接口里
  • 在分拣中,可以定义存取方法,但尽量不要定义属性。

20. 为私有方法名加前缀

一个类所做的政工一般都要比从外侧看来的更多,编写类的落实代码时,经常要写一些在中间使用的措施。应该为这种措施的名目加上一些前缀,这促进调节,因为据此很容易就能把国有艺术和私家方法分别开。

切实选取何种前缀,可依照个人喜好来定,其中最好包含下划线和字母p,比如p_method。不要使用
_method,因为Apple公司欣赏单用一个下划线做个人方法的前缀,可能会挑起争辨。

  • 给个人方法的名号加上前缀,这样可以很容易地将其同国有方法区分开
  • 不要单用一个下划线做个人方法的前缀,因为这种做法是留住苹果集团用的。

16. 提供“全能起先化方法” 

UITableViewCell,起先化该类对象时,需要指明其样式及标示符,标示符可以区分不同品种的单元格,由于这种对象的创始资金较高,所以绘制表格时可遵循标示符来复用,以升级程序效率,我们把这种可为对象提供必要新闻以便其能形成工作的先河化方法叫做“全能先河化方法”。

// 比如创建一个NSDate
- (id)init;
- (id)initWithString:(NSString *)string;
- (id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds;
- (id)initWIthTimeIntervalSinceRefrenceDate:(NSTimeInterval)seconds;

 第两个方法是全能伊始化方法,也就是说其它的起头化方法都要调用它,当底层数据存储机制改变时,只需修改此方法的代码。

  • 在类中提供一个多才多艺初阶化方法,并在文档里指明。其它初叶化方法均应调用此方法
  • 若全能开始化方法与超类不同,则需要复写超类中的对应措施。
  • 假如超类的先导化方法不适用子类,那么相应复写这么些超类方法,并在里面抛出特别。

1. 写这些只是为着自己回忆,有有关pdf文件,如需要留下邮箱。。

5. 用枚举来代表情况、选项、状态码

  • 利用枚举来表示状态机的状态、传递给艺术的选项以及状态码等值,给那些值起个通俗的名字
  • 用NS_ENUM 与 NS_OPTIONS 宏来定义枚举类型,并指明其底层数据类型。
  • 在拍卖枚举类型的switch语句中并非事先default分支,这样的话,参与新枚举之后,编译器就会唤起开发者:switch语句并未处理所有枚举

2. 在类的头文件中尽量少引入其他头文件

  • 唯有确有必要,否则不要引入头文件。一般的话,应在某个类的头文件中应用向前申明来提及其余类(使用@class),并在落实文件中引入这个类的头文件,这样做可以尽可能降低类之间的耦合。
  • 假如要表明某个类遵守某个协议,应该把那么些协议放到分类中,或者把协议单独放在一个头文件中,然后将其引入。

13. 用“方法调配技术”调试“黑盒方法”

重大就是runtime的格局交流,runtime具体可见OC类目中关于runtime的介绍。

大家在这里大概的分析下:

类的法门列表会把采纳子的称呼映射到相关的章程实现直上,使得“动态音讯派发系统”可以据此找到相应调用的艺术,这些办法均以函数指针的花样来代表,这种指针叫做IMP,其原型如下:

id (*IMP)(id,SEL,…)

比如说,NSString
类可以对应lowercaseString、uppercaseString、capitalizedString等选拔子。这张映射表中的每个接纳子都映射到了不同的IMP之上:

图片 4

OC运行期系统提供的多少个主意都可以用来操作这张表,开发者可以向里面新拔取择子,也可以转移某选用子所对应的情势实现,还足以换成五个选项子所映射到的指针,比如我们交流lowercaseString 和 uppercaseString
的法子实现,类的办法表就会变成以下这多少个样子:

图片 5

在新的映射表中,我们得以观看沟通了lowercaseString 和 uppercaseString
的艺术实现,并且多了一个名为newSelector的拔取子,上述修改均无需编写子类,只要修改了“方法表”的布局,就会反映到程序中颇具的NSString实例之上。

透过此方案,开发者能够为这么些“完全不精晓其具体实现”的黑盒方法扩大日志记录效用,这有助于程序调试。

  • 在运行期,可以向类中新增或互换选拔子所对应的不二法门实现。
  • 利用另一份实现来替换原有的法子实现,这道工序叫做“方法调配”,也就是方法交流,开发者常用此技能向原有实现中添加新功能。
  • 相似的话,只有调试程序的时候才需要在运行期修改章程实现,这种做法不宜滥用。

8. 清楚“对象等同性”这一定义

  • 若想检测对象的等同性,请提供“isEqual:”与hash方法
  • 一样的靶子必须具备同等的哈希码,但是五个哈希码相同的对象却不一定相同
  • 并非盲目标相继检测每条属性,而是按照实际需要来指定方案

6. 了解“属性”这一定义

  • 采用@property语法来定义对象中所封装的数码
  • 经过“特质”属性关键字来指定存储数据所需的正确性语义
  • 在安装属性所对应的实例变量时,一定要听从该属性所注脚的语义。

19. 利用清晰而协调的命名模式

给艺术命名时注意事项:

  • 如若艺术的重返值是新成立的,那么方法名的某部词应该是重回值的类型,除非还有修饰语,如:localizedString。属性的存取方法不依照这种命名情势。
  • 应当把代表参数类型的名词放在参数前边。
  • 一旦形式要在脚下目的上举办操作,那么应该包含动词。
  • 并非采纳str这种简称,使用全程。
  • Boolean属性应加is前缀。假如某艺术重回非属性的Boolean值,那么应该按照其功能,接纳has或is当前缀。
  • 将get这些前缀留给那一个借由”输出参数“来保存再次回到值的艺术。

总结:

  • 起名时应听从正规的OC命名规范,这样创制出来的接口更便于为开发者所知道。
  • 主意名要言简意赅
  • 措施名不要使用缩略后的花色名称
  • 给艺术起名时的首先要务就是承保其作风与您自己的代码或所要继承的框架相符。

30. ARC注意事项

  • 在ARC之后,程序员就无需担心内存管理问题了
  • 绝不手动管理
  • CoreFoundation对象不归ARC管理,开发者必须及时调用CFRetain/CFRelease.

4. 多用类型常量,少用#define预处理指令

概念一个常量的章程:

// 第一种:预处理指令
#define ANIMATION_DURATION 0.3

// 第二种:定义静态常量
static const NSTimeInterval kAnimationDuration = 0.3

 我们一般推荐使用第两种,这一个措施定义的常量包含类型音讯,有助于代码阅读。

注意:常量命名法是:若常量局限于“编译单元”(也就是贯彻文件,.m文件)之内,则在头里加字母k;若常量在类之外可见,则一般以类名为前缀。

如我辈需要对外宣布某个常量,我们得以写成下边的代码:

// Test.h
#import <Foundation/Foundation.h>

extern NSString *const TestDidChangeNotification;

@interface Test : NSObject

@end

// Test.m

#import "Test.h"

NSString *const TestDidChangeNotification = @"TestDidChangeNotification";

@implementation Test
  •  不要用预处理指令定义常量。这样定义出来的常量不含类型消息,编译器只是会在编译前依照此执行查找和替换。即便有人重新定义了常量值,编译器也不会有警告,这将促成应用程序中的常量值不均等
  • 在.m文件中应用 static const
    来定义“编译单元内可见常量”,无需加类名前缀,加k
  • 在头文件中运用 extern
    来声称全局常量,并在有关落实公文中定义其值,这种常量要加类名前缀。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图