美文网首页
熟悉Objective-C

熟悉Objective-C

作者: Mark_Lin | 来源:发表于2017-07-18 18:05 被阅读12次

第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语句并未处理所有枚举。

相关文章

网友评论

      本文标题:熟悉Objective-C

      本文链接:https://www.haomeiwen.com/subject/zhbhkxtx.html