美文网首页
《Effective OC》读书笔记(一):熟悉Objectiv

《Effective OC》读书笔记(一):熟悉Objectiv

作者: MichealXXX | 来源:发表于2020-02-23 15:12 被阅读0次

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

    OC和许多面向对象的语言类似,但还是有本质上的差别,OC使用了消息结构,而不是函数调用

    //Objective-C
    Object *obj = [Object new];
    [obj performWith:param and:param1];
    
    //c++
    Object *obj = new Object;
    obj->perform(param,param1)
    

    最关键的区别就是,采用消息结构的语言,其运行时所执行的代码由运行环境来决定,而函数调用的语言,则由编译器决定。函数调用的方法在运行时就已经确定了到底要执行哪个函数实现,而消息结构的语言则是在运行时才去查找要去执行的函数。

    Objective-C的重要工作都由运行时组件来完成,而不是编译器,Objective-C的面向对象特性所需的全部数据结构以及函数都在运行时组件里面。

    Objective-C语言中的指针是用来指示对象的,想要声明一个变量,令其指代某个对象,就如以下语法:

    NSString *someString = @"the string";
    

    它声明了一个名为someString的变量,其类型是NSString *,也就是说此变量是指向NSString的指针,对象所占的内存总是分配在“堆空间”,而不会分配在“”上,someString变量指向分配在堆里的某块内存,其中含有一个NSString对象,如果我们进行以下操作:

    NSString *someString = @"the string";
    NSString *anotherString = someString;
    

    这两个变量会同时指向此对象,当前栈帧中分配了两块内存,每块内存的大小都能容下一枚指针,这两块内存的值都一样,就是NSString实例的内存地址

    OC代码中有时会遇到不含*的变量,它们可能会使用栈空间,这些变量保存的不是OC对象,比如CoreGraphics中的CGRect

    CGRect frame;
    //CGRect是C结构体
    struct CGRect{
      CGPoint origin;
      CGSize size;
    }
    typedef struct CGRect CGRect;
    

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

    C++一样,OC也是用“头文件”和“实现文件”来区隔代码,如果在一个类中引入了另一个类,则需引入其头文件:

    #import "Test.h"
    

    这种方法可行,但不够优雅,如果不需要知道Test类的全部细节,只需要知道有这样一个类就好我们可以使用向前声明:

    @class Test;
    

    但在实现文件中,我们需要知道Test文件的详细内容,则需要使用import来引入。将引入头文件的时机尽量延后,只在却有需要的时候才引入,这样就可以减少类的使用者所需引入的头文件数量,引入过多头文件会增加编译时间,获取有很多用不到的内容也被引入了。

    有时无法使用向前声明,比如要声明某个类要遵循一项协议,这种情况下就把协议的声明迁移至“class-continuation分类”中。

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

    Objective-C1.0开始就可以使用很简单的方法创建NSString对象,这就是“字符串字面量”。

    NSString *someString = @"Effective OC 2.0";
    

    不用这种方法就要以常见的allocinit方法来分配并初始化NSString对象了,现在也可以使用这种方式来声明NSNumber,NSArray,NSDictionary类的实例。这种方式可以缩减源码长度,使其更为易读。

    有时需要把整数、浮点数、布尔值封装到对象中,这时可以使用NSNumber类,该类可以处理多种类型的数值。

    NSNumber *someNumber = [NSNumber numberWithInt:1];
    

    我们还可以更简洁

    NSNumber *someNumber = @1;
    

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

    NSNumber *float = @2.5f;
    NSNumber *double = @3.1415;
    NSNumber *bool = @YES;
    

    数组字典的创建:

    NSArray *array = @[@"cat",@"dog"];
    
    NSDictionary *dictionary = @{
        @"name":@"Kevin"
    }
    

    另外要注意,使用字面量创建出来的字符串,数组,字典都是不可变的,要是想生成可变对象则需要复制一份:

    NSMutableArray *m_Array = [@[@"1",@"2"] mutableCopy];
    

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

    我们经常会使用以下方式声明常量:

    #define ANIMATION_DURATION 0.3
    

    上述指令会把源代码中所有的ANIMATION_DURATION替换为0.3,这可能就是我们想要的结果,不过这样定义出来的常量没有类型信息,加入这个宏被定义在某个头文件中,那么引入这个头文件的代码都会被替换。

    有个方法比使用宏定义更好

    static const NSTimeInterval kAnimationDuration = 0.3;
    

    这种方式定义的常量包含类型信息,更清楚的描述了常量的定义,变量一定要同时用static const来声明,如果试图修改const修饰符所声明的变量,编译器就会报错,而使用static符号则表示该变量仅在定义此变量的编译单元中可见

    如果我们需要定义一个全局可见的的常量,其定义的方式则有所不同,应如下定义:

    //header file
    extern NSString *const EOCString;
    
    //implement file
    NSString *const EOCString = @"VALUE";
    

    const的位置一定要注意,EOCString是一个指针,指向一个NSString对象,我们不希望有人修改它指向其他的NSString对象。extern会告诉编译器在全局符号表中有一个名为EOCString的符号,编译器无需查看其定义就允许使用此常量,这类常量一定要定义,而且只能定义一次。由于是全局可使用,在命名时一定要注意,以免发生冲突。

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

    由于Objective-C基于C语言,所以C语言有的功能它都有,其中之一就是枚举类型,系统框架中频繁使用到此类型。

    枚举只是一种常量命名方式,某个对象所经历的各种状态就可以定义为一个简单的枚举集:

    enum ConnectionState{
      ConnectionStateDisconnected,
      ConnectionStateConnecting,
      ConnectionStateConnected,
    }
    

    每种状态都用一个便于理解的值来表示,所以这样写出来的代码更易于读懂,编译器会为枚举分配一个独有的编号,从0开始,每个枚举递增1,实现枚举所用的数据类型取决于编译器。

    然而定义枚举变量的方式缺不太简洁,要依如下语法编写:

    enum ConnectionState state = ConnectionStateDisconnected;
    

    我们可以使用typedef关键字重新定义枚举类型,来简化声明:

    enum ConnectionState{
      ConnectionStateDisconnected,
      ConnectionStateConnecting,
      ConnectionStateConnected,
    };
    typedef enum ConnectionState ConnectionState;
    

    这样我们就可以用简写的ConnectionState来代替完整的enum ConnectionState了:

    ConnectionState state = ConnectionStateDisconnected;
    

    C++11修改了枚举的某些特性,可以指明用何种“底层数据类型”来保存枚举类型的变量。

    enum ConnectionState:NSInteger{
      ConnectionStateDisconnected = 1,
      ConnectionStateConnecting,
      ConnectionStateConnected,
    }
    

    上述代码把ConnectionStateDisconnected的值设为1,而不是编译器所分配的0.

    还有一种情况应该使用枚举值,那就是定义选项的时候,若这些选型可以彼此组合,则更应该如此,只要枚举定义的对,各选项之间就可以通过按位或操作符来组合:

    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;
    

    系统库中频繁使用这个办法。

    Foundation框架中定义了一些辅助的宏,用这些宏来定义枚举类型时,也可以指定用于保存枚举值的底层数据类型。

    typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
        UIViewAnimationTransitionNone,
        UIViewAnimationTransitionFlipFromLeft,
        UIViewAnimationTransitionFlipFromRight,
        UIViewAnimationTransitionCurlUp,
        UIViewAnimationTransitionCurlDown,
    };
    
    typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    };
    

    凡是需要以按位或操作来组合的枚举都应使用NS_OPTIONS定义,若是枚举不需要互相组合,则应使用NS_ENUM来定义。

    相关文章

      网友评论

          本文标题:《Effective OC》读书笔记(一):熟悉Objectiv

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