【Effective_Objective-C_1熟悉Objective_C】

说在前面的

  • 从Effective_ObjectiveC这本书开始,进入对ObjectiveC的全新学习,如何编写高质量的ObjectiveC代码及如何了解这门语言的内部结构。
  • 这里写一下看了第一章之后的总结和理解
  • 虽然开始看的迷迷糊糊,不过也是一种对基础的复习

熟悉ObjectiveC

  • ObjectiveC是通过一套全新的语法,在C的基础上添加了面向对象的特性,第一章简单的讲解了基础知识

first了解Objective-C的起源

1.消息结构和函数调用
  • 和C++,Java一样,Objective-C也是面向对象语言,但是它们在许多方面都有差别。差别在于Objective-C使用的是消息结构而非函数调用,Objective-C语言由Smalltalk1演化而来的,Smalltalk是消息型语言的鼻祖
  • 消息和函数调用的区别,小🌰例子
//Messaging(Objevtive-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

//Function calling(C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

    • 消息结构和函数调用的关键差别在于:使用消息结构的语言,其运行所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定
    • 对于函数结构,如果范例代码的调用函数是多态的,那么就在运行时按照虚方法表来查出来到底执行哪个函数,而采用消息结构的语言,不管是否为多态总是在运行时才回去查找所要执行的方法。
运行期组件
  • 对于Objective-C的主要工作都由运行期组件(runtime component)而非编译器完成✅
  • 运行期组件本质上是一种与开发者所编写代码相链接的动态库,其代码能够把开发者编写的所有程序粘合起来,这样的话要提升性能即更新运行期组件即可
内存管理
  • Objective-C是C的“超集”(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用。所以要想写出高效的OC代码就得完全掌握OC和C这两门语言,其中尤为重要的是要理解C语言的内存模型(memory medel),这有助于我们理解OC的内存模型和“引用计数”(reference counting)机制的工作原理
    请添加图片描述
  • 例如上述代码,声明了一个someString 的变量,此变量为指向NSString的指针,因为OC声明变量基本上都为指针变量,所以OC对象所占内存总是分配在“堆空间”(heap space)中,而绝不会分配在“栈”(stack)上
  • 如果再次创建一个对象Same,那么这两个对象队徽分配在堆中,它们同时指向了堆中的NSSt ring实例
    请添加图片描述
  • 对象分配在栈上,而实例分配在堆中
  • 分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。
    • 但是Objective-C将堆内存管理抽象成了引用计数
Objective-C的起源要点总结
  • Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期环境而非编译器来决定。
  • 理解C语言的核心概念有助于写好Objective-C程序。尤其是要掌握内存模型和指针。

Second 在类的头文件尽量少饮入其他文件

  • Objective-C采用的是头文件和实例文件区分代码,头文件.h ,实例文件.m,这里以EOCPerson为例格式如下
    • EOCPerson.h
      请添加图片描述
    • EOCPerson.m
      请添加图片描述
  • 对于在EOCPerson里想添加一个新类EOCEmployer,然后去调用EOCEmployer的属性,通常会这样做
    请添加图片描述
  • 然后在EOCPerson的h文件里面添加#import "EOCEmployer.h"文件,你的代码就是这样子的
    请添加图片描述
  • 我们知道在h文件里面是不需要调用EOCEmployer的属性的,也就是编译器不需要知道EOCEmployer的全部细节,只需要有一个EOCEmployer类即可,幸好-向前声明完成了这个要求
  • 向前声明该类,@class + 类,你的头文件变成了这样
    请添加图片描述
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class EOCEmployer;
@interface EOCPerson : NSObject
@property (nonatomic, copy)NSString *eocName;
@property (nonatomic, copy)NSString *eocAge;
@property (nonatomic, strong)EOCEmployer *employer;
@end

NS_ASSUME_NONNULL_END
  • 而当你需要实现EOCEmployer接口的全部细节时候只需要在实现文件里添加#import "EOCEmployer.h"即可
#import "EOCPerson.h"
#import "EOCEmployer.h"

@implementation EOCPerson

@end

尽量延后引入头文件或者单独开辟一个文件
  • 将引入头文件的时机尽量延后,只在需要的时候才引入,这当然会减少编译时间,提升效率
  • 有时候必须要在头文件引入其他头文件,例如遵守某个协议并且必须实现全部细节,这个时候我们可以把该协议单独写一个文件避免了不必要的编译会延期编译效率
向前声明
  • 解决了这两个类相互引用问题
  • 相互引用:有两个类,它们都在头文件中引入了对方的头文件,两个类都进行各自的引用解析,这样就会导致“循环引用”(chicken-and-egg situation)。虽然我们使用#import而非#include不会导致死循环,但是这意味着两个类中有一个类无法被正确编译。
  • 但是,有时候就必须引入头文件,比如继承以及遵循的协议
在类的头文件尽量少饮入其他文件要点总结
  • 除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
  • 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循的协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入,减少不必要的编译,提升性能

THIRD-多用字面量语法,少用与之等价的方法

  • 之前学习疯狂iOS讲义的时候了解过Foundatiob框架,其中包含了NSString,NSNumber,NSarray,NSDictionary类

字面量语法

  • 简单的说就是直接给字符串等变量赋值而不是采用系统方法,这是在Objective-C1.0之后就被允许的
字面量语法NSNumber
  • 系统的方法
NSNumber *number = [NSNumber numberWithInt:19];
  • 字面量语法
    NSNumber *number = @19;
  • 字面量明显相对于系统方法更加的简洁化,以字面量表示🌿数值方法十分有用,可以使NSNumber对象更加简洁。声明里只包含了数值,并无多余成分
字面量数组
  • 创建数组和取数组元素都会更加简洁请添加图片描述
 NSArray* array = [NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil];
    NSString* stringOne = [array objectAtIndex:0];
    NSLog(@"%@", stringOne);
    NSArray* antherArray = @[@"1", @"2", @"3", @"4"];
    NSString* stringone = antherArray[0];
    NSLog(@"%@", stringone);
字面量语法的抛出异常
  • 用字面量创建数组时要注意,若数组元素对象中有nil,则会抛出异常,因为字面量语法实际上是一种“语法糖”(syntactic sugar),其效果相当于是先创建了一个数组,然后把方括号里的所有对象都添加到这个数组中。
  • 例如请添加图片描述
    请添加图片描述
  • 使用arrayWithObjects方法试试
 id obj1 = @"1";
    id obj2 = nil;
    id obj3 = @"3";
    NSArray* array = [NSArray arrayWithObjects:obj1,obj2, obj3, nil];
    NSLog(@"%@", array);![请添加图片描述](https://img-blog.csdnimg.cn/bd84d7c9a4d74328a0880b6d834216c9.png)

请添加图片描述

  • 通过打印结果可以知道对于arrayWithObjects方法系统会依次处理各个参数知道出现nil为止,也就是第二个元素为nil 系统提前结束了数组的创建。
字面量更安全
  • 通过对比可以知道字面量语法会自动抛出异常终止了程序的进行和不必要的错误,我们可以通过异常更快的发现错误
字面量字典
  • 系统的 dictionaryWithObjectsAndKeys方法是这样的初始化
//字面量字典
    NSDictionary *personDict = [NSDictionary dictionaryWithObjectsAndKeys:@"lyt", @"name", @"mh", @"love", [NSNumber numberWithInt:644], @"day", nil];
   
    • 系统的 dictionaryWithObjectsAndKeys方法的value key的格式显得不是很人性化,对于一个day,还不能直接放进去,需要利用NSNumber封装
//字面量字典
    // 字面量更人性化
    NSDictionary* personData = @{@"name": @"lyt", @"love":@"mh", @"day":@644};
  • 字面量语法对比系统的语法看着更简明,其中还有分割每一组对象,理解起来更加顺畅,而且对于数字直接加一个@即可
字面量字典的抛出异常
  • 和数组一样在这里不过多赘述,也是帮助我们及时发现错误!
可变数组和字典的修改
  • 如果数组和对象是可变的,那么也可以通过简单的字面量语法下标修改其中的值
  • 两种方法的对比
mutableArray[1] = @"l y t t t";
mutableDictionary[@"day"] = @"645";

字面语法的局限性
  • 除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象
  • 使用字面量语法创建出来的字符串数组和字典都是不可变的,变成可变的对象需要手动复制方法,再创建一个可变对象.
    • 字面量语法初始化可变对象会警告
      请添加图片描述
    • 当调用的时候会抛出异常,我试着改变里面的内容
 NSMutableArray* array = @[@"1", @"2"];
    [array replaceObjectAtIndex:1 withObject:@"20"];

请添加图片描述

 NSArray* antherArray = @[@"1", @"2", @"3", @"4"];
    NSMutableArray* mutableArray = [antherArray mutableCopy];
//    [mutableArray replaceObjectAtIndex:1 withObject:@"20"];
    mutableArray[1] = @"20";
    NSLog(@"%@", mutableArray[1]);

请添加图片描述

  • 把字面量语法的对象手动变成可变对象虽然要多调用一个方法和创建一个对象,但是利大于弊
字面量语法要点总结
  • 应该使用字面量语法来创建字符串、数值、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
  • 应该通过取下标操作来访问数组下标或者字典中的键对应的元素
  • 用字面量语法创建数组或者字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。
  • 字面量创建字典数组字符串都是不可变的,需要自己再调用一个方法和创建一个可变的对象来避免出现异常

Fourth - 多用类型常量,少用#define预处理命令

  • 这个看的不是很懂,简单的记录一下重点内容
  • 编写代码的时候需要定义常量,在C语言里学过#define预处理
定义常量的命名
#define kString iOS
  • 在实际的开发里面,这样定义出来的常量没有类型信息,并且假设此命令在某个头文件中,那么所有引入了这个头文件的的代码,其定义的固定值都会被这个替换掉,很槽糕!
  • 在树上提供了这样的定义方法
static const NSString* kString = @"iOS";

请添加图片描述

  • 这种方式定义的常量包含类型信息,其好处是清楚的描述了常量的含义,可以看出这个一个NSString* 类型的变量
定义常量的位置方法
  • 定义常量的位置是极其重要的,我们总喜欢在头文件里声明预处理指令,那么引入了这个头文件的所有文件都会含有这个变量,万一重名,程序变得异常麻烦
  • 所以最好不要在头文件中定义常量,不论你是如何定义常量的,因为OC中没有“名称空间”这一概念
static const的好处
  • 不打算公开某个常量,就应该将其定义在使用该常量的实现文件里面,并且变量要用 static const 一起声明。
  • 关于static:使用static意味着该变量仅在定义的文件里面可见,达到了保护变量的作用,这里还涉及到了static关键字,之前有学习过深入理解static关键字
  • 关于const:对于上述static const的字符串,如果仅用const修饰则编译器会为他创建一个外部符号,此时若另一个编译单元也声明了同名变量,那么编译器就会抛出错误信息,这个建议我还没有实现!

问题记录

  • 对于上述说明实际上我测试了一下并没有报错,暂时作为待解决的问题之一吧?
想定义一个全局常量的做法
  • 有时候我们需要对外公开我们的常量,比如说是通知时的通知名称,我们定义一个常量,外界就可以直接使用这个常值变量来注册自己想要接收的通知即可,而不用知道实际字符串的值。此类常量需放在“全局符号表”中,以便可以在定义该常量的编译单元之外使用
  • 定义使用extern关键字
    • 在头文件请添加图片描述
    • 在实现文件、请添加图片描述
  • 注意⚠️:extern就是告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant的符号,也就是说,编译器无需查看其定义。
  • 此常量必须定义而且只允许被定义一次,通常定义在头文件对应的实现文件里面

问题2

  • 我对上面定义的常量进行了一次extren关键字定义,然后在多个文件里面的h文件按照extren关键字也定义了一样的名字类型却没有报错?很疑惑??????
多用类型常量,少用#define预处理命令总结要点
  • 不能用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
  • 在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此常量不在全局符号表中,所以无需为其名称加前缀。
  • 在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应该加以区隔,通常用与之相关的类名做前缀。
  • 我觉得主要注意的点是static const关键字的使用,保留私有化,仅在当前文件调用,保证了安全性

用枚举表示状态选项选项码

  • 这个并不是很懂,看了看要点,觉得自己无法总结出有用的东西
  • 搬运一下书上的总结
  • 应该用枚举表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  • 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可以同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
    用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样可以确保枚举是用开发者所选的底层数据类型实现出来的。而不会采用编译器所选的类型。
  • 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值