iOS - OC 面向对象语法

一、类

  • 1)根类:由于类 NSObject 是层次结构的最顶层,所以称为根类。html

    • 能够将类称为子类(subclass)和父类(superclass),也能够将类称为子类和超类。
  • 2)分类/类别(category):容许以模块的方式向现有类定义添加新的方法(默认不能添加实例变量)。扩展本身或他人之前实现的类,使它适合本身的须要。程序员

    • 分类的名称括在类名以后的一对圆括号“( )”中。objective-c

      @interface QCStudent (Print)
          @end
      
          @implementation QCStudent (Print)
          @end
    • 分类文件名使用符号“+”来分隔类和分类的名字(Xcode 会自动生成)。编程

      QCStudent+Print.m
          QCStudent+Print.h
    • 分类用它能够将类的定义模块化到相关方法的组或分类中。它还提供了扩展示有类定义的简便方式,而且没必要访问类的源代码,也无需建立子类。设计模式

    • 分类能够覆写该类中的另外一个方法,可是一般认为这种作法是拙劣的设计习惯。一个类能够拥有多个分类。使用分类添加新方法来扩展类不只会影响这个类,同时也会影响它的全部子类。分类为现有类添加新方法可能对你有用,但它们可能和该类的原始设计或意图不一致。对象/分类命名对必须是惟一的。数组

    • iOS 开发中,分类默认不容许添加属性。可是若是在本身开发的框架中,但愿在分类中动态添加属性,能够经过 OC 运行时的关联对象功能添加,详见 iOS - OC Category 分类安全

  • 3)类的扩展:有一种特殊的状况是建立一个未命名的分类,而且括号“( )”之间不指定名字。多线程

    • 这种特殊的语法定义称为类的扩展。定义一个像这样的未命名的分类时,能够经过定义额外的实例变量和属性来扩展类,这在有命名的分类中是不容许的。框架

      @interface QCStudent ()
          @end
      
          @implementation QCStudent
          @end
    • 未命名的分类中声明的方法须要在主实现区域实现,而不是在分类的的实现区域。模块化

    • 未命名的分类的方法都是私有的。若是须要写一个类,并且数据和方法仅供这个类自己使用,未命名分类比较合适。

  • 4)抽象类:有时建立类只是为了更容易建立子类。所以,这些类名为抽象类,或等价的称为抽象超类。在该类中定义方法和实例变量,但不但愿任何人从这个类建立实例。

  • 5)类与类之间的关系:

    • 类的包含(复合):
      • 一个类中有另外一个类的 @class 声明。一个类中包含有 #import 另外一个类的 .h 头文件。
      • 通常在 .h 头文件中用 @class 声明一个类,在 .m 文件中使用到该类时再在 .m 文件中包含该类的 .h 头文件。
      #import:文件包含。这种方式会包含被引用类的全部信息,包括被引用类的变量和方法;
      
          @class :类的声明。这种方式只是告诉编译器另外一个类的声明,具体这个类里边有什么信息,这里不须要知道,等实现文件中具体用到时,
                   才会真正的去查看引用类的信息,由于编译器不须要引入和处理整个文件,只需知道是一个类名,在 .h 文件中使用 @class 
                   指令提升了效率。
  • 6)类的加载:

    • 1>、类加载时自动调用方法:+ (void)load;

      + (void)load {
              NSLog(@"%@",@"Student ------------- load");
          }
    • 2>、类首次使用时自动调用方法:+ (void)initialize;

      + (void)initialize {
              NSLog(@"%@",@"Student ------------- initialize");
          }
    • 3>、使用 %@ 打印对象时会自动调用方法:- (NSString *)description;

      // description 覆写
          - (NSString *)description {
      
              return [NSString stringWithFormat:@"age : %d, name : %@", self.age, self.name];
          }
  • 7)是在子类中使用的实例变量,必须先在接口部分声明,而不是在实现部分声明。在实现部分声明和合成的实例变量是私有的,子类中不能直接访问,须要明肯定义或合成取值方法,才能访问实例变量的值。

  • 8)类前缀:

    • 使用 Objective-C 开发 iOS 程序时,最好在每一个类名前面加一个前缀,用来标识这个类。

    • 目的是防止 N 我的开发了同样的类,出现冲突。
      • 好比 Jake Will、Kate Room 在同一个项目中都各自开发了个 Button 类,这样的程序是不能运行起来的。
      • 解决方案:Jake Will 的类名叫作 JWButton,Kate Room 的类名叫作 KRButton。
    • 类前缀的设置

      • Xcode 6 以前:

        • 在建立项目时设置。

          OCObjc1

      • Xcode 6 以后:

        • 建立完项目后设置。

          OCObjc2 OCObjc3

      • 设置完后,再建立新的文件时会自动添加上设置的类前缀。

        OCObjc4

二、对象、方法

  • 1)类的独特存在就是一个实例(对象),对实例执行的操做称为方法。

  • 2)合成对象:能够定义一个类包含其它类的一个或多个对象,这个新类的对象就是所谓的合成对象,由于它是由其它对象组成的。

    • 做为建立子类的代替方法,能够定义一个新类,它包含要扩展类的实例变量。而后,只需在新类中定义适合该类的方法。
  • 3)实例初始化

    • 1>、初始化方式:

      alloc :方法保证对象的全部实例变量都变成初始状态。 建立对象。
          init  :方法用于初始化类的实例变量。              初始化对象。
          new   :能够将 alloc 和 init 的结合起来。        建立并初始化对象。
      • 但用两步来实现建立和初始化的方式一般更好,这样能够在概念上理解正在发生两个不一样的事件:首先建立一个对象,而后对它初始化。
    • 2>、构造方法:实例初始化常见的编程习惯是类中全部初始化方法都以 init 开头。若是但愿在类对象初始化时作一些事情,能够经过重载 init 方法达到这个目的。下面是重载 init 方法的一个标准模板。

      不带参数:
      
              - (instancetype)init {
      
                  self = [super init];
                  if (self) {
      
                      // 初始化代码
                  }
                  return self;    
              }
      
          带参数:
      
              - (instancetype)initWithAge:(int)age andNo:(int)no {    
                  self = [super init];
                  if (self) {
      
                      _age = age;
                      _no = no;   
                  }
                  return self;
              }
      
          类方法:
      
              + (instancetype *)studentWithAge:(int)age {
      
                  Student *stu = [[Student alloc] init];
                  stu.age = age;
      
                  return stu;
              }
      • 执行父类的初始化方法,使得继承的实例变量可以正常的初始化。若是父类初始化成功,返回的值将是非空的。self 用来指明对象是当前方法的接收者。必须将父类 init 方法的执行结果赋值给 self,由于初始化过程改变了对象在内存中的位置(意味着引用将要改变)。

      • 特殊类型 instancetype 代表从 init 方法返回的类型与它的初始化类(也就是初始化消息的接收者)相同。 init 被定义为返回 instancetype 类型,这是编写可能被继承的类 init 方法的通常规则。当编译器碰见 instancetype 做为返回类型,它就知道返回的类型是发送消息的对象。

  • 4)消息:请求一个类或实例来执行某个操做时,就是在向它发送一条消息,消息的接受者称为接收者。

    • OC 采用特定的语法对类和实例应用方法:[类/实例 方法];
  • 5)类方法(静态方法):是对类自己执行某些操做的方法。
    • 实例方法(动态方法):对类的实例执行一些操做。

    • 建立方法名时,参数名其实是可选的,参数名能够省略。如:- (int)set :(int)name :(int)age;

    • 方法(函数)不返回任何值时,无需在方法的末尾执行一条 return 语句。或者也能够执行一条不带任何指定值的 return 语句:return;

  • 6)重写(覆盖):在子类中新建一个与父类中的方法同名的方法。子类中的新方法必须具备相同的返回类型,而且参数的数目和覆写的方法相同。

    • 若是须要来扩展继承的方法。子类中包含对 if (self = [super init]) 的判断。
  • 7)重载:在类中,相同名字不一样参数的方法的写法有一个专门的术语来描述,叫作重载。

  • 8)懒加载

    • 对象在用到时再去加载,并且只加载一次。加载的数据比较大时能够节省内存。
    • 通常重写 getter 方法实现对象的懒加载。

      @property (strong, nonatomic) NSArray *shops;
      
          - (NSArray *)shops {
      
              // 加载数据
              if (_shops == nil) {
                  NSString *filePath = [[NSBundle mainBundle] pathForResource:@"shops" ofType:@"plist"];
                  _shops = [NSArray arrayWithContentsOfFile: filePath];
              }
      
              return _shops;
          }

三、数据封装(实例变量)

  • 1)数据封装:将实例变量隐藏起来的这种作法实际上涉及一个关键概念 --“数据封装”。

    • 不能在类的外部编写方法直接设置或获取实例变量的值,而须要编写设置方法和取值方法来设置和获取实例变量的值,这即是数据封装的原则。
    • 必须经过使用一些方法来访问这些一般对“外界”隐藏的数据,这种作法集中了访问实例变量的方式,而且可以阻止其它一些代码直接改变实例变量的值。若是能够直接改变,会让程序很难跟踪、调试和修改。
  • 2)实例变量的定义做用域

    @public      全局均可以访问,实例对象可使用符号 “->” 直接访问实例变量。   
        @protected   只能在类内部和子类中访问 (访问器方法 默认) 。
        @private     只能在类内部访问 (合成取值方法 默认)。
    
        @package     经常使用于框架类的实例变量,同一包内能用,跨包就不能访问。
  • 3)访问器方法(accessor):取值方法和设值方法一般称为访问器方法。一般实例变量声明时如下画线( _ )字符开头,此实例变量默认为保护(@protected)的。在类内部和子类中均可以访问。

    • 设值方法(setter):设置实例变量值的方法一般总称为设值方法。定义时在实例变量名前加上 set。如:

      // ARC
          - (void)setAge:(NSNumber *)age {
      
              _age = age;
          }
      
          // MRC
          - (void)setAge:(NSNumber *)age {
      
              if (_age) { 
                  [_age release]; 
              } 
              _age = [age retain];
          }
    • 取值方法(getter):用于检索实例变量值的方法叫作取值方法。定义时直接使用实例变量名。如:

      - (NSNumber *)age {
      
              return _age;
          }
  • 4)合成取值方法:一般实例变量声明时不如下画线( _ )字符开头,以字母开头,而且此实例变量是私有(@private)的。只能在类内部访问。

    • 在接口部分中使用 @property 指令标识属性,声明实例变量的 setter 和 getter 方法。 - 如:@property int numerator, denominator;

    • 在实现部分中使用 @synthesize 指令标识属性,实现实例变量的 setter 和 getter 方法。 - 如:@synthesize numerator, denominator;

    • 1>、若是使用了 @property 指令,就不须要在实现部分声明相应的实例变量。固然也能够再声明相应的实例变量,可是那不是必需要作的,编译器会有一些提示。

      • 能够不使用 @synthesize 指令,使用 @property 指令就足够了,编译器会自动为你生成 setter 和 getter 方法(声明并实现),可是注意若是你不使用 @synthesize ,那么编译器声明的实例变量会如下划线( _ )字符做为其名称的第一个字符。
    • 2>、@property 的修饰

      • 在不写任何修饰时,Xcode 会自动生成标准的 setter 和 getter 方法,写修饰时 Xcode 会自动生成带内存管理的 setter 方法,标准 getter 方法。

      • 参数分类:

        读写属性:readwrite/readonly
            setter 处理:assign/retain/copy
            原子性:atomic/nonatomic
            方法名:setter = method / getter = method
            引用型:strong/weak
            可选性:nonnull/nullable/null_unspecified/null_resettable    // Xcode 7 新增特性
        
            readwrite:可读写,生成 setter 和 getter 方法。默认。
            readonly :只读,只生成 getter 方法。
        
            assign   :修饰普通类型,在 setter 方法中直接赋值。默认。简单赋值,不更改引用计数。
                            如:@property (nonatomic, assign)int age;
            retain   :修饰 OC 对象,在 setter 方法中 release 旧值,retain 新值。释放旧的对象,将旧对象的值赋予输入对象,再提升输入对象的引用计数为 1。
                            如:@property (nonatomic, retain)Dog *dog;
            copy     :修饰 NSString 类型,在 setter 方法中 release 旧值,copy 新值。创建了一个相同的对象,地址不一样(retain:指针拷贝 copy:内容拷贝)。
                            如:@property (nonatomic, copy)NSString *name;
        
            atomic   :原子性,默认。是 OC 使用的一种线程保护技术,防止在写入未完成的时候被另一个线程读取,形成数据错误。
                            给 setter 和 getter 方法加锁,保证多线程安全。
            nonatomic:非原子性,禁止多线程,变量保护,提升性能。不给 setter 和 getter 方法加锁,执行相对快点。
        
            setter = method:指定 setter 方法的方法名。 
                            如:@property (nonatomic, setter = setIsRich)BOOL rich; 
                                将 rich 的  setter 方法重命名为 setIsRich 。
            getter = method:指定 getter 方法的方法名。 
                            如:@property (nonatomic, getter = isRich)BOOL rich; 
                                将 rich 的  getter 方法重命名为 isRich 。
        
            strong   :强引用,在 OC 中对象默认都是 strong。(ARC 下的)和(MRC)retain 同样 (默认)。
                            viewController 对根视图是强引用,view addSubviews 方法是向数组中添加子视图,数组会对子视图强引用。
            weak     :弱引用,weak 的做用,一旦没有强引用,会被当即释放。(ARC 下的)和(MRC)assign 同样。
                            苹果从 StoryBoard 拖线默认是 weak。weak 当指向的内存释放掉后自动 nil 化,防止野指针。
        
            nonnull         :不可为空
            nullable        :能够为空
            null_unspecified:不肯定是否能够为空(极少状况)
            null_resettable :set 方法能够为 nil,get 方法不可返回 nil,只能用在属性的声明中。
  • 5)点运算符(点语法):访问的是方法(setter/getter 方法),不是实例变量。

    • 合成取值方法中可使用点运算符访问属性,也能够对自定义的方法使用点运算符,如语句 myFraction.print ,并未考虑编码风格是否良好。

    • 点运算符一般用在属性上,用于设置或取得实例变量的值。作其它工做的方法一般不是由点运算符执行的,而是使用传统的方括号形式的消息表达式做为首选的语法。

  • 6)尖运算符(->):当实例变量定义为 @public 类型时,实例对象可使用符号 “->” 直接访问实例变量。 如:car -> _speed = 80; int a = car -> _speed;

  • 7)局部变量:是基本的 C 数据类型,并无默认的初始值,因此在使用前要先赋值。
    • 局部对象变量:默认初始值为 nil 。

      • 方法的参数名也是局部变量。执行方法时,经过方法传递的任何参数都被复制到局部变量中。由于方法使用参数的副本,因此不能改变经过方法传递的原值。
      • 若是参数是对象,能够更改其中的实例变量值。当你传递一个对象做为参数时,其实是传递了一个数据存储位置的引用。
    • 静态变量:在局部变量声明前加上关键字 static ,可使局部变量保留屡次调用一个方法所得的值。静态变量的初始值为 0 。

      • 不少状况下想要将变量定义为全局变量,但不是外部变量,能够在包含这个特定类型实现的文件中将这个变量定义为 static 。
    • 外部变量:在方法外定义的变量不只是全局变量,并且是外部变量。

      • 使用外部变量时,必须遵循下面这条重要原则:变量必须定义在源文件中的某个位置。即在全部的方法和函数以外定义变量,而且前面不加关键字 extern 。在全部的函数以外声明变量,在声明前面加上关键字 extern 。

      • 处理外部变量时,变量能够在许多地方声明为 extern ,可是只能定义一次。

四、继承、多态

  • 1)继承的概念做用于整个继承链。

    • 类的每一个实例(对象)都拥有本身的实例变量,即便这些实例变量是继承来的。

    • 继承一般用于扩展一个类。不能经过继承删除或减小方法。

    • 为何须要建立子类:(1)但愿继承一个类的函数,也需加入一些新的方法和/或实例变量。(2)但愿建立一个类的特别版本。(3)但愿经过覆写一个或多个方法 来改变类的默认行为。

  • 2)多态:使不一样的类共享相同方法名称的能力称为多态。可以使来自不一样类的对象定义相同名称的方法。

  • 3)动态类型:id 类型的对象。在运行时而不是编译时肯定对象所属的类,能使程序直到执行时才肯定对象所属的类。
    • 静态类型:将一个变量定义为特定类的对象时,使用的是静态类型。静态指的是对存储在变量中的对象类型进行显示的声明。
    • 动态绑定:在运行时而不是编译时肯定对象须要调用的方法,能使程序直到执行时才肯定实际要调用的对象方法。

      • OC 系统老是跟踪对象所属的类。
      • id 类型的对象先断定对象所属的类(动态类型),而后在运行时肯定须要动态调用的方法,而不是在编译的时候(动态绑定)。

      • 为何还要关心静态类型:(1)它能更好的在程序编译阶段而不是运行时指出错误。(2)它能提升程序的可读性。

      • 若是使用动态类型来调用一个方法,须要注意一下规则:若是在多个类中实现名称相同的方法,那么每一个方法都必须符合各个参数的类型和返回值类型,这样编译器才能为消息表达式生成正确的代码。

    • 处理动态类型的方法:

      • 如下总结了 NSObject 类所支持的一些基本方法,其中,class-object 是一个类对象(一般是由 class 方法产生的),selector 是一个 SEL 类型的值(一般是由 @selector 指令产生的)。

        - (BOOL)isKindOfClass:class-object                    // 对象是否是 class-object 或其子类的成员
            - (BOOL)isMemberOfClass:class-object                  // 对象是否是 class-object 的成员
            + (BOOL)isSubclassOfClass:class-object                // 某个类是不是指定类的子类
        
            - (BOOL)respondsToSelector:selector                   // 对象是否可以响应 selector 所指定的方法
            + (BOOL)instancesRespondToSelector:selector           // 指定的类实例是否能响应 selector
        
            - (id)performSelector:selector                        // 应用 selector 指定的方法
            - (id)performSelector:selector withObject:object      // 应用 selector 指定的方法,传递参数 object
            - (id)performSelector:selector withObject:object1 withObject:object2    // 应用 selector 指定的方法,传递参数 object1 和 object2
        
            [Square class]          // 从名为 Square 的类中得到类对象
            [mySquare class]        // 知道对象 mySquare 所属的类
        
            @selector(alloc)        // 为名为 alloc 的方法生成一个 SEL 类型的值。
  • 4)消除 performSelector: 方法警告

    #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    
        // performSelector: 方法
    
        #pragma clang diagnostic pop

五、协议、代理

  • 1)协议:是多个类共享的一个方法列表。协议中列出的方法没有相应的实现,计划由其余人来实现。协议中列出的方法,有些是能够选择实现,有些是必须实现。

    • 1>、若是你定义了本身的协议,那么没必要由本身实现它。可是,这就告诉其余程序员,若是要采用这项协议,则必须实现这些方法。这些方法能够从超类继承。

      • 协议不引用任何类,它是无类的。

      • 分类也能够采用一项协议。

    • 2>、定义一个协议很简单:只要使用 @protocol 指令,后面跟上你给出的协议名称。定义一项协议时,能够扩展示有协议的定义。

      @protocol PlayerDelegate <NSObject>
      
          - (void)end;
      
          @end
    • 3>、协议的修饰

      @optional:该指令以后列出的全部方法都是可选的。
          @required:该指令以后列出的全部方都是必须实现的,默认。因为 OC 是弱语法,虽然字面上是必须,但编译器并无强求实现。
    • 4>、协议的声明

      @protocol  protocol-name
    • 5>、协议的检查

      // 检查一个对象是否遵照某项协议。
          - (BOOL)conformsToProtocol:(Protocol *)aProtocol;
      
              // 用于获取一个协议名称,并产生一个 Protocol 对象,conformsToProtocol: 方法指望这个对象做为它的参数。
              @protocol(Drawing)
      
          // 检查对象是否可以响应 selector 所指定的方法。
          - (BOOL)respondsToSelector:selector
      
              // 为名为 alloc 的方法生成一个 SEL 类型的值。
              @selector(alloc)
  • 2)非正式协议:其实是一个分类,列出了一组方法但并无实现它们。非正式协议一般是为根类定义的,有时,非正式协议也称为抽象协议。

    • 声明非正式协议的类本身并不实现这些方法,而且选择实现这些方法的子类须要在它的接口部分从新声明这些方法,同时还要实现这些方法中的一个或多个。

    • 指令 @optional 添加到 OC 2.0 语言中,用于取代非正式协议的使用。

  • 3)代理:协议也是一种两个类之间的接口定义。定义了协议的类能够看做是将协议定义的方法代理给了实现它们的类。

    • 代理设计模式的做用:
      • 一、A 对象监听 B 对象的一些行为,A 成为 B 的代理
      • 二、B 对象想告诉 A 对象一些事情,A 成为 B 的代理
    • 代理设计模式的总结:
      • 一、若是你想监听别人的一些行为,那么你就要成为别人的代理
      • 二、若是你想告诉别人一些事情,那么就让别人成为你的代理
    • 代理设计模式的开发步骤:
      • 一、拟一份协议(协议名字的格式:控件名 + Delegate),在协议里面声明一些代理方法(通常代理方法都是 @optional)
      • 二、声明一个代理属性:@property (nonatomic, weak) id <代理协议> delegate;
      • 三、在内部发生某些行为时,调用代理对应的代理方法,通知代理内部发生什么事
      • 四、设置代理:xxx.delegate = yyy;
      • 五、yyy 对象遵照协议,实现代理方法

六、为何 Objective-C 的方法调用要用方括

  • 为何 Objective-C 的方法调用要用方括号 [obj foo],而不是别的语言经常使用的点 obj.foo ?

    • 首先要说的是,Objective-C 的历史至关久远,若是你查 wiki 的话,你会发现:Objective-C 和 C++ 这两种语言的发行年份都是 1983 年。在设计之初,两者都是做为 C 语言的面向对象的接班人,但愿成为事实上的标准。最后结果你们都知道了,C++ 最终胜利了,而 Objective-C 在以后的几十年中,基本上变成了苹果本身家玩的玩具。不过最终,因为 iPhone 的出现,Objective-C 迎来了第二春,在 TOBIE 语言排行榜上,从 20 名开外一路上升,排名曾经超越过 C++,达到了第三名(下图),可是随着 Swift 的出现,Objective-C 的排名则一路下滑。

    • Objective-C 在设计之初参考了很多 Smalltalk 的设计,而消息发送则是向 Smalltalk 学来的。Objective-C 当时采用了方括号的形式来表示发送消息,为何没有选择用点呢?我我的以为是,当时市面上并无别的面向对象语言的设计参考,而 Objective-C 「发明」了方括号的形式来给对象发消息,而 C++ 则「发明」了用点的方式来 “发消息”。有人可能会争论说 C++ 的「点」并非真正的发消息,可是其实两者都是表示「调用对象所属的成员函数」。

    • 另外,有读者评论说使用方括号的形式是为了向下兼容 C 语言,我并不以为中括号是惟一选择,C++ 不也兼容了 C 语言么?Swift 不也能够调用 C 函数么?

    • 最终,实际上是 C++ 的「发明」显得更舒服一些,因此后来的各类语言都借鉴了 C++ 的这种设计,也包括 Objective-C 在内。Objective-C 2.0 版本中,引入了 dot syntax,即:

      a = obj.foo 等价于 a = [obj foo]
          obj.foo = 1 则等价于 [obj setFoo:1]
    • Objective-C 其实在设计之中确实是比较特立独行的,除了方括号的函数调用方式外,还包括比较长的,可读性很强的函数命名风格。

    • 我我的并不讨厌 Objective-C 的这种设计,可是从 Swift 语言的设计来看,苹果也开始放弃一些 Objective-C 的特色了,好比就去掉了方括号这种函数调用方式。

    • 因此,回到咱们的问题,我我的认为,答案就是:Objective-C 在 1983 年设计的时候,并无什么有效的效仿对象,因而就发明了一种有特色的函数调用方式,如今看起来,这种方式比点操做符仍是略逊一筹。

    • 大多数语言一旦被设计好,就很难被再次修改,应该说 Objective-C 发明在 30 年前,仍是很是优秀的,它的面向对象化设计得很是纯粹,比 C++ 要全面得多,也比 C++ 要简单得多。

相关文章
相关标签/搜索