iOS—Effective Objective—C2.0(1)

了解OC起源

面向对象语言OC,在语法上使用的是“消息结构”,而非“函数调用”

消息结构和函数调用之间的区别

//messaging(OC)
object* obj = [object new];
[obj performWith:parameter1 and:parameter2]; 
//function calling (C++)
object* obj = new object;
obj -> perform(parameter1, parameter2);

关键区别:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言则由编译器来决定。如果范例代码中调用的函数是多态的,那么在运行时就要按照“虚方法表”来查出到底应该执行哪个函数实现。而采用消息结构的语言,不论是否多态,总是在运行时才会去查找所要执行的方法实际上,编译器不关心接受消息的对象是何种勒烯女。接收消息的对象问题也要在运行时候处理,其过程叫做“动态绑定”
OC的重要工作都由“运行期组件”而非编译器来完成。使用OC的面向对象特性所需的全部数据结构及函数都在运行期组件李敏啊。举例来说,运行期组件中含有全部的管理内存的方法。运行期组件本质上就是一种与开发者所编代码相链接的“动态库”,其代码能把开发者编写的所有程序粘合起来。这样的话,只需要更新运行期组件,即可提升应用程序性能。而那种许多工作都需要在“编译期”完成的语言,若是想要获得类似的性能提升,则需要重新编译应用程序代码。
OC是C的“超集”所以c语言中的所有功能在编写OC代码的时候依然适用。因此必须同时掌握C与OC这两门语言的核心概念,才能写出高效的OC代码来。
其中尤为重要的是要理解C语言的内存模型,这有利于理解OC的内存模型及“引用计数”机制的工作原理。
理解内存模型:OC中的指针是用来指示对象的,想要声明一个变量,令其指代某个对象,可用以下语法
NSString* someString = @"THE String";
这种语法基本上是照搬c语言的,他声明了一个名为someString的变量,类型为NSString*也就是说,此变量为指向NSString的指针,所有OC语言的对象都必须这样声明,因为所占的内存总是分配在“堆空间”中,而绝不会分配在“栈上”,不能在栈上分配OC对象
在这里插入图片描述

someString变量指向分配在堆里的某块内存,其中含有一个NSString对象。也就是说,如果再创建一个变量,令其指向同一地址,那么并不拷贝该对象,只是这两个变量会同时指向此对象
在这里插入图片描述
这说明当前“栈帧”里面分配了两块内存,每块内存的大小都能容下一枚指针(32位4字节,64位8字节)。两块内存里面的值是一样的,就是NSString实例的内存地址
在这里插入图片描述
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。
OC将堆内存管理抽象出来了。不需要用malloc和free来分配或释放对象所占内存。OC运行期环境把这部分工作抽象为一套内存管理架构,名叫“引用计数”
在OC代码中有时会遇到定义不含*的变量,他们可能会使用栈空间,这些变量保存的不是OC对象 CGRect就是例子
在这里插入图片描述
CGRect是c结构体

struct CGRect {
    CGpoint origin;
    CGSize siza;
};
typedef struct CGRect CGRect;

整个系统框架都在使用这种结构体,如果改用OC对象来做的话,性能会受影响,与创建结构体相比,创建对象还需要额外开销,例如分配释放堆内存等,如果只需要保存 int float double char等“非对象类型”那么通常使用CG热潮天、这种结构体就行

要点

  • OC为C语言添加了面向对象的特性,是其超集。OC使用动态绑定的消息结构,在运行时才会检查对象类型。接收一条消息之后,在接受一条消息后,应该执行何种代码,由运行期环境而非编译器来决定。
  • 理解c语言的核心概念有助于写好OC程序,尤其要掌握内存模型与指针

虚方法表

virtual method table 是编程语言为实现“动态派发”或“运行时方法绑定”而采用的一种机制。

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

与C和C++一样使用头文件与实现文件来区隔代码,用OC语言编写“类”的标准方式为:以类名做文件名,分别创建两个文件,头文件后缀用.h,实现文件后缀用.m,创建好一个类之后,其代码看上去如下所示


//
//  EOCPerson.h
//  EOC1.1
//
//  Created by zzy on 2022/12/22.
//
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString* firstName;
@property (nonatomic, copy) NSString* lastName;

@end

NS_ASSUME_NONNULL_END

//  EOCPerson.m
//  EOC1.1
//
//  Created by zzy on 2022/12/22.
//

#import "EOCPerson.h"

@implementation EOCPerson

@end

OC语言编写任何类基本都需要引入Foundation框架,如果不引入这个文件的话,那么就要引入与其超类所属框架相对应的“基本头文件”,在创建iOS应用程序时,通常会继承UIViewController类,而这些子类的的头文件需要引入UIKit.h
EOCPerson类其头文件中引入了整个Foundation框架,那如果此类继承自Foundation框架中的某个类,那么EOCPerson类的使用者可能会用到其基类的中的许多内容。继承自UIViewController的那些类也是如此,其使用者可能会用到UIKit中的大部分内容。
你创建了一个新类EOCEmployer,然后你觉得每个EOCPerson实例都需要有一个EOCEmployer于是添加了一个属性
@property (nonatomic, strong) EOCEmployer *employer;
但你需要引入#import "EOCEmployer.h",这种办法可行,但是不够优雅,在编译一个使用了EOCPerson类的文件时,不需要知道EOCEmployer的具体细节,只需要知道有一个类名叫EOCEmployer就好,有一个办法可以将这一情况告诉编译器
@class EOCEmployer;
“向前声明”,现在EOCPerson的头文件就是这样

//  EOCPerson.h
//  EOC1.1
//
//  Created by zzy on 2022/12/22.
//

#import <Foundation/Foundation.h>

@class EOCEmployer;

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString* firstName;
@property (nonatomic, copy) NSString* lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

EOCPerson类的实现文件则需要引入EOCEmployer类的头文件,因为若要使用后者,则需要知道所有接口细节,实现文件就需要引入#import "EOCEmployer.h",将引入头文件的时机尽量延后,只有在确有需要时才引入,以减少类的使用者所引入的头文件数量,假设本例把EOCEmployer.h引入到EOCPerson.h,那么只需要引入EOCPerson.h就可以一并引入EOCEmployer的所有内容了,但这样需要引入许多根本用不到的内容,增加了不必要的编译时间。

  • 向前声明也解决了两个类互相引用的问题。如果在各自的头文件引用了对方的头文件,会导致循环引用,当解析其中的一个头文件时编译器会发现它引入了另一个头文件,而那个头文件又回过头来引用第一个头文件,虽然使用import而非include虽然不会导致死循环,但这意味着两个类里面有一个无法被正确编译。
    有时候必须要在头文件中引入其他头文件。如果你写的类继承自某个超类,则必须引入定义那个超类的头文件。
    同理,若要声明你写的类遵从某个协议,那么该协议必须拥有完整定义,且不能使用向前声明,向前声明只能告诉编译器有这个协议,而此时编译器是需要知道该协议中定义的方法。
    若要因为实现属性,实例变量或者要遵循协议而必须引入头文件,则应尽量将其移至“class—continuation”分类中,这样做不仅可以缩短编译时间,而且还能降低彼此的依赖程度,若是依赖关系太过复杂,则会给维护带来麻烦,而且,如果只是想把代码的某个部分开放为公共API的话,太复杂的依赖关系也会出问题

@class include<> import"" import<>的区别

#include 是 C/C++ 导入头文件的关键字 是完整的包含某个文件的内容(包括该文件中被导入的文件)

#import 是 OC 导入头文件的关键字

#import 指令是 OC 针对 #include 的改进版本,使用#import ,能确保引用的文件只会被引用一次,不会出现递归包含的问题

#import<> 代表导入 系统自带的框架

#import"" 代表导入我们自己创建的类,导入的是我们的 .h文件,也就是头文件

#import 会链入该头文件的全部信息,包括 实例变量和 方法 等。

@class 的作用: 告诉编译器,我后面跟着的字符串是 类的名称,其余的你不用管!所以用@class的类内部的 实例变量和方法 都不能访问!

编译效率方面区别:@class 在某些情况下编译效率高于 #import

如:你有100个头文件都#import了同一个头文件;或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有  引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。

但是用@class则不会。

编译错误方面:

如:如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,

如果使用#import来相互包含,那么就会出现编译错误,

如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现

重复引用方面:#import 可以使用不会出错,#include 不可以避免错误 #include 会有重复引用的错误

如:A 类导入了 C 类的头文件,B 类也导入了 C 类的头文件,D 类又同时导入 A 和 B 类,这就是重复导入 #import则不会有重复引用的问题

要点

  • 除非有必要,否则不要引入头文件,一般来说,应该在某个类的头文件中使用向前声明来提及别的类,并在实现文件中英茹那些类的头文件,这样做可以尽量降低类之间的耦合
  • 有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把该类遵循某协议的这条声明移动至“class-continuation分类中”如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

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

在使用oc时要经常用到Foundation框架,经常用到NSString,NSNumber,NSArray,NSDictionary。从类名上即可看出各自所表示的数据结构。
有一种方式能非常简单的方式能创建NSString对象:“字符串字面量”,NSString *someString = @"EOC2.0";如果不用这种语法的话就要用常见的alloc init方法来分配并初始化NSString对象了,这种方法可以缩减源代码长度,使其更为易读。

字面量数值

能够以NSNumber实例表示的所有 数据类型都可以使用该语法。

    NSString *someString = @"EOC2.0";
    NSNumber *intNumber = @1;
    NSNumber *floatNumber = @2.5f;
    NSNumber *doubleNumber = @3.14159;
    NSNumber *boolNumber = @YES;
    NSNumber *charNumber = @'a';
    //字面量语法也适用于以下表达式;
    int x = 5;
    float y = 6.32f;
    NSNumber *expressionNumber = @(x * y);

字面量数组

数组是常用的数据结构,非字面量语法创建实例:

NSArray *season = [NSString arrayWithOjbects:@"spring",@"summer",@"autumn",@"winter",nil];

使用字面量语法:

NSArray *season = @[@"spring",@"summer",@"autumn",@"winter"];

这种做法不仅简单,而且利于操作数组。数组常见操作就是取某个下标所对应的对象,如果不用字面量操作:

NSString *sum = [season objectAtIndex:1];

使用字面量:

NSString *sum = season[1];

要注意,若数组元素对象中有nil,则会抛出异常,字面量语法实际上只是一种语法糖,其效果等于是先创建了一个数组,然后把方括号内的所有对象都添加进数组,如果发现nil则会停止添加(arrayWithObjects:方法会依次处理各个参数,直到发现nil为止)
使用字面量语法会更为安全,出现异常会令程序终止执行,这比创建好数组之后才发现元素个数少了要好。

字面量字典

Dictionary ,映射型数据结构,键值对

NSDictionary *personData = [NSDictionarydictionaryWithObjectsAndKeys:

                @"Matt", @"firstName", 

                @"Galloway", @"lastName", 

                [NSNumber numberWithInt:28], @"age",   

                 nil];

顺序是 《对象》,《键》 与

通常理解为 把“键”映射到“对象”,相反

字面量:

  NSDictionary *personData = @{@"firstName" : @"Matt",

                       @"lastName" : @"Galloway",

                     @"age" : @28};

28区别原因:字典中的对象和键必须都是Objective-C对象,所以不能把28直接放进去,而要封装在NSNumber实例中才行

由键访问其值 : 不用字面量: NSString *lastNamge = [personData objectForKey:@“lastName”];

使用字面量:NSString *lastName = personData[@“lastName”];

可变数组与字典:

修改可变数组与字典内容的标准做法是:


[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];

[mutableDictionary setObject:@"Galloway"forKey:@"lastName"];

换成去下标:

  mutableArray[1] = @"dog";

  mutableDictionary[@"lastName"] = @"Galloway";

局限性:

除了字符串以外,所创建出来的对象必须属于Foundation框架。

使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)

想要可变版本的对象,需复制一份:NSMutableArray *mutable = [@[@1, @2, @3]mutableCopy];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值