熟悉Objective-C

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

  • Objective-C为C语言添加了面向对象特性,是其超集。理解C语言的内存模型(memory model)有助于理解Objective-C的内存模型及其引用计数(reference counting)机制的工作原理。
  • Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。在接收一条消息之后,究竟应执行何种代码,有运行期环境决定,而非编译器决定。

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

  • 当我们在编译一个类的时候,不需要知道该类的全部细节,只需要知道有一个这样的类就好的时候,我们就不用通过#import的方式来引入该类头文件。

    这叫“向前声明”(forward declaring)该类。

    @class "EOCEmployer.h"
    

    import和@class的区别

    import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称。

    在头文件中, 一般只需要知道被引用的类的名称就可以了。不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。

    而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。

    如果存在循环依赖关系,如:A -> B, B -> A这样的相互依赖关系,当使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。

    所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import。

  • 当我们需要声明某个类遵循一项协议时,不能使用@class。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其通过#import的方式引入。

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

  • 使用字面量语法来声明可以缩减源代码长度,使其更为易读。
字面数值
NSNumber *Number = [NSNumber numberWithInt:1];
NSNumber *newNumber = @1;
字面数组

如果数组元素对象中有nil,则会抛出异常,因为字面量语法实际上只是一种“语法糖”(syntactic sugar),其效果等于是先创建了一个数组,然后把方括号内的所有对象都加入到这个数组中。
因此,如下面这两个数组的第二位都未nil时,array里面会只有@“one”一个对象,而newArray将会抛出异常。这种微妙的差别表明,使用字面量语法更为安全。抛出异常令程序中止执行,这比创建好数组之后才发现元素个数少了要好,因为向数组中插入nil通常就是说明程序有错,最终结果往往就是数组越界崩溃,而现在我们却可以通过异常可以更快地发现这个错误。

NSArray *array = [NSArray arrayWithObjects:@"one",@"two",@"three", nil];
NSArray *newArray = @[@"one",@"two",@"three"];
字面字典

与数组一样,用字面量语法创建字典时也有一个问题,那就是一旦有值为nil,便会抛出异常。

NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
                        @"Mark",@"firstname",
                        @"Lin",@"lastname",nil];

NSDictionary *newDic = @{@"firstname" : @"Mark",
                         @"lastname" : @"Lin",
                         @"age" : @28};

大家可以很直观地看出两种创建实例变量的方法有什么区别,使用字面量的方式不仅可以令到代码看起来更加地整洁,而且字面量语法也更为精简,因为声明中只包含数值,而没有多余的语法成分。

局限性

字面量语法有一个小小的局限,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行。

而使用字面量语法创建的字符串、数组、字典都是不可变的(immutable),若想要可变版本的对象可以参考以下写法:

NSMutableArray *mutable = [@[@"one", @"two", @"three"] mutableCopy];

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

  • 编写代码时经常需要定义一些常量,很多人都会用下面这种方式来实现:

    #define ANIMATION_DURATION 0.3
    

    这种实现方式可能就是你想要的效果,但是这样定义出来的常量没有类型信息,此外,预处理过程会把所有ANIMATION_DURATION一律改成0.3,这样的话,假设此指令在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。

    要想解决这个问题,可以使用下面这种方式来实现:

    static const NSTimeInterval kAnimationDuration = 0.3;
    

    此方式定义的常量包含类型信息,清晰描述常量的含义。而且定义常量的位置很重要,在头文件中声明预处理指令是一种很糟糕的做法,常量名称有可能互相冲突。因为Objective-C没有“名称空间”(namespace)这一概念,所以这样做等于声明了一个全局变量。

    所以若不打算公开某常量,则应将其定义在使用该常量的实现文件里面,如下:

    //  MarkAnimatedView.h
    
    #import <UIKit/UIKit.h>
    
    @interface MarkAnimatedView : UIView
    
    - (void)animate;
    
    @end
    
    //  MarkAnimatedView.m
    
    #import "MarkAnimatedView.h"
    
    static const NSTimeInterval kAnimationDuration = 0.3;
    
    @implementation MarkAnimatedView
    
    - (void)animate
    {
        [UIView animateWithDuration:kAnimationDuration animations:^{
            //Perform animations
        }];
    }
    
    @end
    

常量为什么一定要同时使用static与const来声明呢???
因为如果有人试图修改有const修饰符所声明的变量时,那编译就会报错,而这就是我们所需要达到的目的!!!

而static修饰符则是意味着该常量仅在定义此常量的编译单元中可见。

编译器每收到一个编译单元,就会输出一份“目标文件”(object file),在Objective-C的语境下,“编译单元”一词通常指每个类的实现文件,因此在上述代码中声明的kAnimationDuration,其作用域仅限于由MarkAnimatedView.m所生成的目标文件中。

假如声明此常量时不加static,则编译器会为它创建一个“外部符号”(external symbol),此时如若另一个编译单元中声明了同名常量,则会报错。

而有时候确实是需要对外公开某个常量的时候,我们有应该如何处理呢?(如在类代码中调用NSNotificationCenter)

此类常量则应放在“全局符号表”(global symbol table)中,以便可以在定义常量的编译单元之外使用。具体实现如下:

```
//  MarkAnimatedView.h
#import <UIKit/UIKit.h>

extern NSString *const MarkLoginNotification;

@interface MarkAnimatedView : UIView

- (void)animate;

@end

//  MarkAnimatedView.m
#import "MarkAnimatedView.h"

static const NSTimeInterval kAnimationDuration = 0.3;

NSString *const MarkLoginNotification = @"MarkLoginNotification";

@implementation MarkAnimatedView

- (void)animate
{
   [UIView animateWithDuration:kAnimationDuration animations:^{
        //Perform animations
    }];
}

- (void)doNotificationAction
{
    [[NSNotificationCenter defaultCenter] postNotificationName:MarkLoginNotification object:nil];
}

@end
```

注意const修饰符在常量类型中的位置,常量定义应从右至左解读,所以在本例子中,MarkLoginNotification和kAnimationDuration就是“一个常量,而这个常量是指针,指向NSString对象”。

extern这个修饰符会告诉编译器,在全局符号表中将会有一个MarkLoginNotification的符号,编译器无须查看其定义,因为它知道当链接成二进制文件之后,肯定能找到这个常量。

这样定义常量要优于#define预处理指令,因为编译器会确保常量值不变,而且一旦定义好之后,即可随处使用。

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

  • 枚举只是一种常量命名方式, 使用枚举来表示各种状态码可以便于程序猿们更好地去理解代码。如使用枚举来表示“套接字连接”(socket connection)的状态:

    enum MarkConnectionState {
        MarkConnectionDisconnected,
        MarkConnectionConnecting,
        MarkConnectionConnected,
    };
    
  • 要想每次不用敲入enum而只需写MarkConnectionState的话,则需要使用typedef关键字重新定义枚举类型:

    enum MarkConnectionState {
        MarkConnectionDisconnected,
        MarkConnectionConnecting,
        MarkConnectionConnected,
    };
    typedef enum MarkConnectionState MarkConnectionState;
    

    现在就可以使用简写的MarkConnectionState来代替完整的enum MarkConnectionState了:

    MarkConnectionState state = MarkConnectionConnected;
    
  • C++11标准修订了枚举的某些特性,其中就包括了很重要的一点:可以指明使用何种“底层数据类型”(underlying type)来保存枚举类型的变量。这样做的好处就是可以向前声明枚举变量了:

    enum MarkConnectionState : NSInteger;
    

    当然,还可以不使用编译器所分配的序号,而手工指定某个枚举成员所对应的值:

    enum MarkConnectionState {
        MarkConnectionDisconnected = 22,
        MarkConnectionConnecting,
        MarkConnectionConnected,
    };
    

    如前所述,由于第一个枚举值为22,所以接下来的几个枚举值都会在上一个的基础上递增1。

  • 还有一种情况是非常推荐使用枚举类型的,那就是-----定义选项!

    enum MarkAutoresizing {
        MarkAutoresizin         = 0,
        MarkAutoresizinWidth    = 1 << 0,
        MarkAutoresizinHeight   = 1 << 1,
        MarkAutoresizinTop      = 1 << 2,
        MarkAutoresizinBottom   = 1 << 3,
    };
    
  • 在iOS UI框架中的UIKit里也有一个非常常见的例子,那就是用枚举值来告诉系统视图所支持的设备显示方向,这个枚举类型叫做UIInterfaceOrientationMask,开发者可以通过supported InterfaceOrientations的方法,将视图所支持的显示方向告诉系统。

  • 最后再讲一种枚举的用法,在swith中使用枚举:

    typedef NS_ENUM(NSUInteger, MarkConnectionState){
        MarkConnectionDisconnected,
        MarkConnectionConnecting,
        MarkConnectionConnected,
    };
    
    switch (_currentState) {
            case MarkConnectionDisconnected:
                
                break;
            case MarkConnectionConnecting:
                
                break;
            case MarkConnectionConnected:
                
                break;
            default:
                break;
        }
    

使用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型,这样做可以确保枚举是用开发者所选的底层数据类型实现出来,而不会采用编译器所选的类型。
在处理枚举类型的swith语句中不要实现default分支,这样就可以在加入新枚举之后,编译器自动提示开发者:swith语句并未处理所有枚举。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容