第1条:了解Objective-C语言的起源

Objective-C语言由Smalltalk1演化而来的,Smalltalk是消息型语言的鼻祖,所以OC使用的是“消息结构”(messaging structure)而非“函数调用”(function calling)。

1.消息与函数调用之间的区别:

//Messaging(Objevtive-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];//Function calling(C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

关键区别在于: 使用消息结构的语言,其运行所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。

Objective-CC的“超集”(superset),所以C语言中的所有功能在编写Objective-C代码时依然适用。所以要想写出高效的OC代码就得完全掌握OCC这两门语言,其中尤为重要的是要理解C语言内存模型(memory medel),这有助于我们理解OC内存模型和“引用计数”(reference counting)机制的工作原理。

2.OC的内存管理:

NSString stackString;
//error:interface type cannot be statically allocatedNSString *someString = @"The string";

因为OC声明变量基本上都为指针变量,所以OC对象所占内存总是分配在“堆空间”(heap space)中,而绝不会分配在“”(stack)上。

对象分配在栈上,而实例分配在堆中。
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。

3.要点:

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

第2条:在类的头文件中尽量少引入其他头文件

CC++一样,Objective-C也使用“头文件”(header file)与“实现文件”(implementation file)来区隔代码。

1.用Objective-C编写“类”(class)的标准方式:

以类名做文件名,分别创建两个文件,头文件后缀用.h实现文件后缀用.m

//EOCPerson.h
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@end//EOCPerson.m
#import "EOCPerson.h"
@implementation EOCPerson
//Implementation of methods
@end

2.在一个文件中引入另一个文件(向前声明):

通常我们在一个文件中要引入另一个文件我们就需要在其加入另一个文件的头文件即.h文件,如下:

//EOCPerson.h
#import <Foundation/Foundation.h>
#import "EOCEmployer.h"
@interface EOCPerson : NSObject
@end//EOCPerson.m
#import "EOCPerson.h"
@implementation EOCPerson
//Implementation of methods
@end

这种方法可行,但是不够优雅。我们不需要让其知道EOCEmployer类的全部细节,只需要让其知道有一个这样的类就行,所以可以采用下面的方法:

//EOCPerson.h
#import <Foundation/Foundation.h>
@class EOCEmployer;
@interface EOCPerson : NSObject
@end//EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"
@implementation EOCPerson
//Implementation of methods
@end

这叫做“向前声明”(forward declaring)该类。
注意: 在.h文件中因为只需要有这个类就行所以可以只使用@class EOCEmployer;添加,但是在.m实现文件中因为要用到该文件的具体细节内容,所以要加上#import "EOCEmployer.h"头文件。

将引入头文件的时机尽量延后,只在需要的时候才引入,这样就可以减少类的使用者所需引入头文件的数量。

3.向前声明的好处:

向前声明也解决了两个类相互引用的问题。

例如:有两个类,它们都在头文件中引入了对方的头文件,两个类都进行各自的引用解析,这样就会导致“循环引用”(chicken-and-egg situation)。虽然我们使用#import而非#include不会导致死循环,但是这意味着两个类中有一个类无法被正确编译。
但是,有时候就必须引入头文件,比如继承以及遵循的协议。

4.要点:

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

第3条:多用字面量语法,少用与之等价的方法

字面量语法说白了就是不用系统给的初始化方法,而是直接对一个变量进行赋值,就和C语言相似,比如:

NSString *someString = @"shuaiGeGe";

当然也可以用这种语法来声明NSNumber、NSArray、NSDictionary类的实例,并且使用这种语法可以缩减源代码长度,使其更为易读。

1.字面数值:

原来我们对NSNumber类进行初始化时采取系统的初始化方法:

NSNumber *someNumber = [NSNumber numberWithInt:1];

而使用字面量语法可以直接进行赋值:

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//对运算也适用
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);

由此可以看出字面量语法非常的简洁,没有任何多余的语法成分。

2.字面量数组:

之前创建一个数组:

NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];

而使用字面量语法来创建:

NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

上面的创建方法不仅简单而且还利于操作数组,就比如访问数组的元素,之前是:

NSString *dog = [animals objectAtIndex:1];

使用字面量就可以直接:

NSString *dog = animals[1];

这也叫做“取下标”操作(subscripting),跟其他的也相同,这种方式更加简洁、更易理解。
注意: 用字面量创建数组时要注意,若数组元素对象中有nil,则会抛出异常,因为字面量语法实际上是一种“语法糖”(syntactic sugar)1,其效果相当于是先创建了一个数组,然后把方括号里的所有对象都添加到这个数组中,然而使用自带的初始化方法,它是当遇到nil时就会停止返回,所以我们可以利用这个特性来判断数组初始化是否正确。
1:也称“糖衣语法”,是指计算机语言中与另外一套语法等效但是开发者用起来却更加方便的语法。语法糖可令程序更易读,减少代码的出错几率。

3.字面量字典:

官方初始化字典变量,两两一对,<对象>,<键>:

NSDictionary *personData = [NSDictionary dictionaryWithObjectivesAndKeys:@"Mett", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];

这样写与我们通常理解的模式不太相同,理解起来可能会有点麻烦,所以我们可以使用字面量定义:

NSDictionary *personData = @{@"firstName": @"Matt", @"lastName": @"Galloway", @"age": @28};

这样写我们理解起来就简单的多了,并且这个与数组相同,只要遇到nil就会抛出异常,这有助于查错。
当然字典变量的访问也可以使用字面量方法:

NSString *lastName = personData[@"lastName"];

这样写也省去了冗赘的语法,令此代码更简单易读。

4.可变数组与字典:

通过取下标操作,可以访问数组中的某个元素或者字典中的某个键对应的元素,如果数组和字典是可变的(mutable),那么也能通过下标修改其中的元素值,例如:

mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

5.局限性:

字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。

6.要点:

  • 应该使用字面量语法来创建字符串、数值、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
  • 应该通过取下标操作来访问数组下标或者字典中的键对应的元素。
  • 用字面量语法创建数组或者字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil

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

通常我们在写程序的时候都会使用#define来定义一个固定的数据,方便我们后续自己的编写,但是这样定义出来的常量没有类型信息,并且假设此命令在某个头文件中,那么所有引入了这个头文件的的代码,其定义的固定值都会被这个替换掉,反而破坏了程序。

那么这个时候我们就可以使用下面的方法:

static const NSTimeInterval kAnimationDuration = 0.3;

这种方式定义的常量包含类型信息,其好处是清楚的描述了常量的含义。由此可知,该常量类型为NSTimeInterval,这有助于其编写开发文档。

1.常量常用的命名法:

若常量局限于某“编译单元”(也就是“实现文件”)之内,则在前面加字母k;若常量在类之外可见,则通常以类名为前缀。

2.常量的定义位置:

常量定义的位置也非常重要,我们最好不要将常量定义在头文件中,若你定义在头文件中,又被其他的文件引用了,那么该这个文件中的这个常量都会被其替换掉,所以最好不要在头文件中定义常量,不论你是如何定义常量的,因为OC中没有“名称空间”这一概念。

3.使用static const而不用#define的原因:

使用static const来声明一个常量,如果试图修改由const修饰符所声明的变量,那么编译器就会报错。static修饰符则意味着该变量仅在定义此变量的编译单元中可见。在OC中“编译单元”通常指每个类的实现文件.m文件)。而#define不具有这些功能,所以我们使用static const来修饰。
假如声明此变量时不加static,则编译器会为它创建一个“外部符号”。此时若是另一个编译单元中也声明了同名变量,那么编译器就会抛出错误信息。

4.定义一个全局常量:

有时候我们需要对外公开我们的常量,比如说是通知时的通知名称,我们定义一个常量,外界就可以直接使用这个常值变量来注册自己想要接收的通知即可,而不用知道实际字符串的值。
此类常量需放在“全局符号表”中,以便可以在定义该常量的编译单元之外使用。举例说明:

//In the header file
extern NSString *const EOCStringConstant;//In the implementation file
NSString *const EOCStringConstant = @"VALUE";

因为常量定义应该从右向左解读,所以它是一个不可变的指向NSString *类型的指针,这个指针的指向不允许被改变。
extern就是告诉编译器,在全局符号表中将会有一个名叫EOCStringConstant的符号,也就是说,编译器无需查看其定义,即允许代码使用此常量。
又因为符号要放在全局符号表里,所以我们就得更加注意其命名,不能出现重名的情况,其名称应该严谨严谨再严谨!!!

5.要点:

  • 不能用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
  • 在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此常量不在全局符号表中,所以无需为其名称加前缀。
  • 在头文件中使用extern来声明全局变量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应该加以区隔,通常用与之相关的类名做前缀。

第5条:用枚举表示状态、选项、状态码

枚举在我们写iOS程序时十分的常见,比如按钮的状态之类的,它都是使用枚举来定义的,我们相应的状态只能使用一种枚举状态,但是还有的情况我们可以选取多个枚举值,比如获取通知传值的新值和旧值这就是一个多状态的枚举。

1.单项枚举:

单项枚举通常都是使用数字作为编号来逐一增加编号的值来确定的,默认的编号从0开始,比如:

enum EOCConnectionState {EOCConnectionStateDisConnected,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};

这里的EOCConnectionStateDisConnected就为0,而EOCConnectionStateConnected就是2了。当然你也可以自行去定其枚举的初始编号:

enum EOCConnectionState {EOCConnectionStateDisConnected = 1,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};

但是,通常使用枚举的话定义变量的类型太长了,而且不太简洁:

enum EOCConnectionState = EOCConnectionStateDisConnected;

这时候我们就可以使用typedef来重新命名:

typedef enum EOCConnectionState EOCConnectionState;

这样EOCConnectionState就代表了之前的enum EOCConnectionState就简洁了很多了。
除了这些,编译器还更新了指定底层的数据类型,所用的语法是:

enum EOCConnectionState : NSInteger {/*...*/};

这样这个枚举类型数据的底层数据类型就是NSInteger了,我们也可以使用向前声明的方法:

enum EOCConnectionState : NSInteger;

2.多项枚举:

多项枚举其实就是使用2的幂次方来表示一个枚举数值,对底层二进制了解的程序员可能更容易理解。所以这里的多项枚举其实就是在其二进制的特殊性上来演变出来的,通过“按位或操作”将选择的枚举直接进行或运算得到一个得数,通过这个得数就可以确定用户选择的状态。例如:

enum UIViewAutoresizing {UIViewAutoresizingNone = 0,UIViewAutoresizingFlexibleLeftMargin = 1 << 0,UIViewAutoresizingFlexibleWidth = 1 << 1,UIViewAutoresizingFlexibleRightMargin = 1 << 2,UIViewAutoresizingFlexibleTopMargin = 1 << 3,UIViewAutoresizingFlexibleHeight = 1 << 4,UIViewAutoresizingFlexibleBottomMargin = 1 << 5,
};

这里的每个选项均可启用或者禁用,例如:

enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;if (resizing & UIViewAutoresizingFlexibleWidth) {//UIViewAutoresizingFlexibleWidth is set
}

它的原理其实就是这样:

当然多项枚举和单项枚举也一样,都可以自行确定枚举类型数据的底层数据类型,与上述写法相同。

3.与Switch语句的结合:

typedef NS_ENUM (NSUInteger, EOCConnectionState) {EOCConnectionStateDisConnected,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};switch (_currentState) {EOCConnectionStateDisConnected://Handle disconnected statebreak;EOCConnectionStateConnecting://Handle connecting statebreak;EOCConnectionStateConnected://Handle connected statebreak;
}

通常我们喜欢在switch语句的最后加上default分支,但是这里不要用!!!因为你本就是枚举变量,使用default分支如果用户没有使用枚举中的类型,那么这个switch语句也会进入default分支,就与我们的本意不符了,还会造成程序的错误,所以这里不要用default分支!!!

4.要点:

  • 应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
  • 如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
  • 用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
  • 在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

  1. 20世纪70年代出现的一种面向对象语言,详情参见:Smalltalk ↩︎ ↩︎ ↩︎

【Effective Objective-C】—— 熟悉Objective-C相关推荐

  1. 【Effective Objective-C】——熟悉Objective-C

    文章目录 第一条:了解Objective-C的起源 第二条:在类的头文件中尽量少引入其他头文件 第三条:多用字面量语法,少用与之等价的方法 字面数值 字面量数组 字面量字典 可变数组与字典 局限性 第 ...

  2. [Effective Objective] 熟悉Objective-C

    了解 Objective-C Objective_C 是一种面向对象的语言.但与jave.C++等语言不同,它使用了消息结构(messaging structure)而非函数调用(function c ...

  3. Paper:GPT-3《 Language Models are Few-Shot Learners》的翻译与解读

    Paper:GPT-3< Language Models are Few-Shot Learners>的翻译与解读 目录 <GPT-3: Language Models are Fe ...

  4. [转载] 使用神经网络和ml模型预测客户流失

    参考链接: Keras中的深度学习模型-探索性数据分析(EDA) This story is a walk-through of a notebook I uploaded on Kaggle. Or ...

  5. android有用的命令

    转载请指明出处:草帽的后花园 文件1:下载 Android Tools详解 aapt aapt即Android Asset Packaging Tool , 在SDK的platform-tools目录 ...

  6. 基于Android4.0.3的各种工具信息整理(共130个)

    昨日下午,老大交给一个任务,就是对android编译出来的build/host/linux-x86/bin下面的各种可执行程序进行一个了解 于是我就花了一天的时间来搜集信息,大致有两个文件 一个是比较 ...

  7. Paper:GPT-3之《 Language Models are Few-Shot Learners》的翻译与解读

    Paper:GPT-3之< Language Models are Few-Shot Learners>的翻译与解读 目录 <GPT-3: Language Models are F ...

  8. 5分钟掌握手动优化机器学习模型超参数

    机器学习算法具有超参数,可让这些算法针对特定的数据集进行量身定制. 尽管通常可以理解超参数的影响,但是可能不知道它们对数据集的特定影响以及它们在学习期间的交互作用.因此,作为机器学习项目的一部分,调整 ...

  9. 今日arXiv精选 | 13 篇 ICCV 2021 最新论文

     关于 #今日arXiv精选  这是「AI 学术前沿」旗下的一档栏目,编辑将每日从arXiv中精选高质量论文,推送给读者. A QuadTree Image Representation for Co ...

最新文章

  1. java 计算信度,11.5.2 评分者信度实例分析
  2. ACL’22 | 为大模型定制的数据增强方法FlipDA,屠榜六大NLU 数据集!
  3. h0152. 故事计算题(计蒜客——西邮K题)解析
  4. 万众期待的PowerBI Report Server与PowerBI Premium
  5. 面向对象程序设计(OOP设计模式)-行为型模式之观察者模式的应用与实现
  6. 11种刷新按钮的方法
  7. 汇编语言和C语言的比较
  8. 阿里云 oss 图片在 img 中访问失败,浏览器中正常访问
  9. 3dmax中如何设置环境灯光
  10. Html论坛提问页面,技术分享 - 制作论坛发帖页面(采用html()方式、操作节点的方式)...
  11. uos操作系统安装mysql
  12. Blockchains Distributed L week3 爱宝授课记录(2)
  13. java获取剩余手机电池容量_怎样判断手机电池的剩余容量
  14. Android出现没有资源包问题,Android打包出现的小问题汇总
  15. 优秀课件笔记之计算机网络基础
  16. 主域控崩溃后,备域如何快速接管主域控制器
  17. pycharm下django案例的环境搭建运行
  18. 系统文件host的作用
  19. 如何测试数字硅麦软件,硅麦参考电路及layout注意事项.PDF
  20. 每日一词20190318——图像金字塔(image pyramid)

热门文章

  1. 科技新品 | 创维比手机还纤薄的电视;TCL三款智慧伴学平板电脑;三星扩充车用内存产品阵容...
  2. JavaScript设计模式理解
  3. css 样式手形,css 添加手状样式
  4. 脚本红客联盟:有史以来批处理最完整人性化教程
  5. 千锋为你解读Android培训课程体系
  6. 系统检测到您正在使用网页抓取工具访问_造成Baiduspider(百度蜘蛛)抓取网站异常的原因有哪些...
  7. MySQL笔记之多表
  8. 【windows start命令】windows类似nohup命令
  9. 洛谷P1042 [NOIP2003 普及组] 乒乓球
  10. 工作中的点滴感悟,职场小白要牢记