熟悉Objective-C

一、了解起源

oc为c语言添加了面向对象的特性,是其超集,oc用的是动态绑定的消息结构,即运行时才会检查对象类型。接受一条消息究竟执行何种代码,由运行期环境而非编译器决定。(因而很多时候,我们刚写出来程序没问题,进行到某一步可能就突然崩溃了)

oc将堆的内存管理抽象出来了,不需要通过malloc与free来分配或释放对象所占的内存,并把这套内存管理架构称为"引用计数",对象所占据的内存总是分配在"堆空间"中,绝不会被分配在栈上

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

除非在类的头文件中应尽量减少 #import 其他头文件,优先使用 @class 进行前向声明,这样可以避免循环引用问题,并减少编译依赖,提高编译效率

当两个类在头文件中互相 #import 时,会形成循环依赖,导致编译时某个类的完整定义尚未解析,从而出现编译错误

因此,在头文件中如果只是使用对方类的"指针类型",应使用 @class 进行声明

只有在 .m 文件中真正需要访问该类的属性或方法实现时,才 #import 头文件

  1. @class只能用于指针
objc 复制代码
@class Person;
@property (nonatomic, strong) Person *person; 

这样是可以的

但是如图这样就不行,原因是

  • 指针只需要知道"这个类型存在"
  • 实例变量需要知道"这个类的完整结构"

而 @class 只是"声明存在",没有完整定义。

  1. 什么时候必须#import

    • 使用该类作为实例变量(非指针)

    • 使用该类作为父类

    • 访问该类的属性或方法

    • 使用该类的宏、typedef

  2. 循环引用的本质原因

    头文件在编译阶段会被展开替换,如果两个头文件互相 import,就会互相包含,导致类型在尚未完整声明前被使用,从而报错。

总而言之,在 Objective-C 中,头文件应尽量避免使用 #import,优先使用 @class 进行前向声明。@class 只声明类的存在,适用于声明指针类型。如果需要访问类的属性、方法或继承关系,则必须 #import,这样可以避免循环依赖,降低耦合,并提高编译效率。

三、多用字面量语法,少用等价的其他方法

我们总会使用到某几个属于Foundation框架的类,如一下NSString,NSNumber,NSArray,NSDictionary的类,而我们现在能用非常简单的几个方法来创建这几个的对象,这就是"字符串常量"

NSString

objc 复制代码
NSString *str = @"Hello";
  • @"Hello" 是 编译期常量
  • 存储在常量区
  • 不能修改(NSString 本身就是不可变的)

NSNumber

objc 复制代码
NSNumber *num = @10;
NSNumber *flag = @YES;
NSNumber *pi = @3.14;

变量要加括号

objc 复制代码
int a = 5;
NSNumber *num = @(a);

NSArray

objc 复制代码
NSArray *arr = @[@"A", @"B", @"C"];

注意:数组不能包含nil,会崩溃,使用字面量会直接报错,而使用之前arrayWithObjects方法时可能会把nil及其后面的对象遗失,所以使用字面量语法也可以更安全,帮我们早早发现异常

而我们如果提前添加时可以先用if判断再加,或者用?:的三元表达式来进行添加

NSDictionary

objc 复制代码
NSDictionary *dict = @{
    @"name": @"Tom",
    @"age": @18
};

如果key或者value值为空时,就会报错,此时还是想上面一样,用if判空或者用三元表达式进行判断

可变数组/可变字典不能用字面量进行直接创建

对于可变的数组或者字典我们要用一下的方法来进行创建,因为字面量所创建的对象全都是不可变的

objc 复制代码
NSMutableArray *arr = [@[@"A", @"B"] mutableCopy];
[arr addObject:@"C"];

或者

objc 复制代码
NSMutableArray *arr = [NSMutableArray array];

四、多用类型常量,少用#define预处理指令

在编写代码时要定义常量时,若我们使用类似#define的方法来直接定义常量,但是用这种方法直接定义时,有可能会把我们在某个头文件中与这个常量相同名字的都直接进行替换了,所以我们就要用下面这种优化后更好用的方法,比如

objc 复制代码
static const NSTimeInterval kAnimationDuration = 0.3;

用这种方法定义的常量包含了类型信息,清晰表明了常量的含义

命名方法:若该常量局限于编译单元(即实现文件内),则常在前面加字母k;

若在类之外可见,则通常以这个类名为前缀

变量一定要用static const同时来声明,若修改用const修饰的变量,就会报错;static修饰则意味着该变量仅在此变量的编译单元内可见,若声明此变量时不加static,则编译器会为其创建一个"外部符号",此时若另一个编译单元也声明了同名变量,则会报错

实际上,若一个变量即声明为static有声明为const,编译器也不会创建符号,而是像#define预处理指令一样,把遇到的变量直接替换为常值,不过还是要记住:这样定义的常量带有类型信息

创建符号指:在 .o 文件里生成一条名字记录,供链接器使用

"不创建符号"就是:编译器不会在目标文件的符号表里为这个变量生成一条可链接的名字记录,因为它已经在编译期被当成常量直接替换掉了。

编译流程:源代码->编译->目标文件(.o)->符号表->链接->可执行文件

有时候也需要对外公开某个常量,在多个.m文件使用同一个常量,就不能再用static const了,因为static只在当前编译单元可见,这时候要用extern + const

用法:

在.h文件中声明:

objc 复制代码
#import <Foundation/Foundation.h>

extern const NSTimeInterval PTAnimationDuration;

这里的作用

  • extern → 告诉编译器"这个变量在别处定义"
  • 只声明,不分配内存

在.m文件中定义:

objc 复制代码
#import "Constants.h"

const NSTimeInterval PTAnimationDuration = 0.3;
  • 真正分配内存
  • 真正创建符号
  • 供链接器使用

extern的意思是:

这个变量定义在别的编译单元,我这里只是引用它

它的作用是:

  • 不分配内存
  • 不生成定义
  • 只告诉编译器"别报未声明错误"

(字符串常量)注意:

我们经常会看到:

objc 复制代码
// PTNotificationConstants.h
extern NSString * const PTPlayerDidStartNotification;
objc 复制代码
// PTNotificationConstants.m
NSString * const PTPlayerDidStartNotification = @"PTPlayerDidStartNotification";

我们这里用的是NSString * const,而不是const NSString *

objc 复制代码
NSString * const name

意思是

指针不可变(地址不能改),但字符串内容本身不可变(因为 NSString 是不可变类)

若写为:

objc 复制代码
const NSString * name

意思是:

字符串对象不可修改(没意义,因为 NSString 本来就不可变)

相关推荐
abcy07121320 小时前
flink窗口类型
开发语言·python·算法
-银雾鸢尾-20 小时前
C#中的继承
开发语言·c#
zhanghaha131420 小时前
Python语言基础:2_环境搭建
开发语言·python
Wang's Blog1 天前
Go-Zero项目开发4: 用户服务搜索、详情与统一错误处理
开发语言·golang
我星期八休息1 天前
网络编程—应用层HTTP协议
linux·运维·开发语言·前端·网络·网络协议·http
梅雅达编程笔记1 天前
零基础学 Python 第14章 | 模块、包与第三方库
开发语言·python·django·numpy·pandas
Mr__Miss1 天前
Java泛型完全指南:从入门到精通
java·开发语言·python
赵庆明老师1 天前
Vben精讲:14-Vben远程加载语言包
开发语言·vben
星辰即远方1 天前
字符串合法性检验
macos·ios·cocoa