OC语法之-核心语法

多文件开发

  1. C语言中我们知道,多文件开发是将函数的实现全部写在.c文件(one.c),函数的声明全部写在.h文件(one.h),然后只要将.h文件导入(#include”one.h”)带有主函数的.c(two.c)文件即可,然后分别编译one.c/two.c文件得到one.o/two.o文件,然后一起连接这两个.o文件生成可执行文件a.out,然后运行即可.
  2. 如果我们在two.c文件中导入的不是.h文件而是one.c文件,当同时进行连接one.o/two.o时,就会报错:说我们重复定义,因为#include相当于copy,two.c中已经有了one.c的全部内容,因此如果在把他们一起连接那么实际上就会有两份one.c了
  3. 对比C语言来看,OC也同理,为了方便,我们也进行多文件开发,分为.h/.m文件,因此也不能导入.m文件,否者也会报错:重复定义,XCode编译连接时会把所有的.m一起编译连接
  4. 同C语言对比一样,我们把每一个类单独用一对.m/.h文件来写,通常这个.m/.h文件的名字为该类的名字;.h中放类的声明,.m文件中放类的实现
  5. 与C语言中不同的是,.m文件中一定要导入对应的.h文件,因为.m文件中用到了.h文件类的声明的成员变量跟方法
  6. 如果哪个文件用到了该类,直接将该类的.h文件导入即可,千万不可以导入.m文件,错误跟C语言一样,重复定义
  7. 从上面我们可以看出.h跟.m没有任何必然联系系,不用一样的名字也可以
  8. XCode编译是把所有的.m文件编译成.o(目标文件),然后连接是把所有的.o文件连接在一起形成可执行文件a.out

点语法

#import <Foundation/Foundation.h>
@interface Person : NSObject
{
    int _age;
    NSString *_name;
}

- (void)setAge:(int)age;
- (int)age;

@end

#import "Person.h"
@implementation Person

- (void)setAge:(int)age {
    //_age = age;
    
    NSLog(@"setAge:");
    
    // 会引发死循环
    //self.age = age; // [self setAge:age];
}
- (int)age {
    NSLog(@"age");
    return _age;
    // 会引发死循环
    //return self.age;// [self age];
}
@end

//点语法调用:
 Person *p = [Person new];
//点语法的赋值就是调用set方法
p.age = 10; // [p setAge:10];
//点语法的取值就是调用get方法
int a = p.age; // [p age];
  1. 点语法跟set方法/get方法有关的
  2. 这个点不是访问这个对象的成员变量的(不是age也不是_age)(这个最容易误解!!!),java中的点语法是访问成员变量的
  3. 点语法的本质是方法调用
  4. 编译器遇到点语法时,会自动转化为方法调用,因此点语法是编译器特性(编译器特性)
  5. 点语法的赋值就是调用set方法

     p.age = 10; <=>[p setAge:10];
    
  6. 点语法的取值就是调用get方法

     int a = p.age; <=> [p age];
    
  7. 满足点语法的条件:
    1. 有set/get方法的声明
    2. 有set/get方法的实现
    3. set/get方法实现中,可以没有成员变量操作
    4. 比如:

       1. 声明
        - (void)setAge:(int)age;
        - (int)age;
        2.实现:
        - (void)setAge:(int)age
        {
        NSLog(@"这里随便写");
        }
        - (int)age
        {
        NSLog(@"这里随便写");
        //return 1;
        }
      
    5. 没有set方法,就只能使用点语法的取值操作
    6. 没有get方法,就只能使用点语法的赋值操作
    7. 注意: 同样适用于类方法Person * p = Person.new

成员变量的作用域

  1. 局部变量/全局变量都有他的作用域,而成员变量既不属于局部变量,也不属于全局变量,那么他的作用域是什么呢?
  2. 局部变量与全局变量的作用域相对的是代码块,即在代码块内有用还是在代码块外有用;那么成员变量的作用域就是相对于这个类了,即在类中有用,还是在类外有用
  3. 相对于类内还是类外分为以下4种:
    1. @private: 只能在当前类的对象方法中直接访问(@implementation中设置成员变量默认是@private,类扩展中设置也是!!!)
      1. 注意了,在 @implementation中或者类扩展中声明的成员变量虽然默认是@private,但是也只能是@private,即使加上@public也没有用,因为别人包含或者继承这个类的时候,只会包含你的.h文件!!!
    2. @protected: 可以在当前类及其子类的对象方法中直接访问(在@interface中默认就是@protected)
    3. @public: 在任何地方(类内部、外部)都能直接访问对象的成员变量
    4. @package: 同一个体系(框架)内可以访问,介于@private@public之间
  4. 注意: @interface@implementation中不能声明同名的成员变量
  5. 代码举例:

     //Person.h文件
     #import <Foundation/Foundation.h>
        
     @interface Person : NSObject
     {
         int _no;
         // 在任何地方都能直接访问对象的成员变量
         @public 
         int _age;
         // 只能在当前类的对象方法中直接访问
         @private  
         int _height;
         // 能在当前类和子类的对象方法中直接访问
         @protected 
         int _weight;
         int _money;
     }
        
     - (void)setHeight:(int)height;
     - (int)height;
     - (void)test;
     @end
        
     //Person.m文件
     #import "Person.h"
     @interface Person()
     {   // 类扩展中:默认就是私有@private
         //说明:类扩展中声明成员变量<=>@implementatiom中声明成员变量
         int _klkl;
         @public//没有用仍然是@private类型
         int _lkk;
     }
     @end
        
     @implementation Person
     {
         int _aaa;// 默认就是私有@private
            
         @public//写上这个也没有用,因为别人包含或者继承这个类的时候,只会包含你的.h文件!!!
         int _bbb;
         // @implementation中不能定义和@interface中同名的成员变量
         // int _no;
     }
        
     - (void)test {
         //只能在当前类实现中访问
         _age = 19;
            
         _height = 20;
            
         _weight = 50;
            
         _aaa = 10;
     }
        
     - (void)setHeight:(int)height {
         _height = height;
     }
        
     - (int)height {
         return _height;
     }
     @end
        
     //Student.h文件
     #import "Person.h"
        
     @interface Student : Person
     - (void)study;
     @end
        
     //Student.m文件
     #import "Student.h"
     @implementation Student
     - (void)study {
         //不能在子类中直接访问
         // _height = 10;
         //用set/get方法设置,方法是可以继承的
         [self setHeight:10];
         int h = [self height];
         //内在子类中直接访问
         _weight = 100;
         //不能在子类中直接访问
         //_klkl = 10;
     }
     @end
        
     //main.m文件
     int main(int argc, const char * argv[]) {
        
         @autoreleasepool {
             Student *stu = [Student new];
             [stu setHeight:100];
             NSLog(@"%d", [stu height]);
        
             Person *p = [Person new];
             p->_age = 100;
              //@public没用!!!
             //p->_bbb = 10;
             //只能在当前类中访问
             //p->_height = 20;
             //前类和子类
             //p->_weight = 10;
         }
         return 0;
     }
    

@property和@synthesize

  1. 这两个关键字帮助我们自动生成get/set方法,跟点语法一样,也是编译器特性
  2. Xcode4.4之前

        
     #import <Foundation/Foundation.h>
        
     @interface Person : NSObject
     {
         //下面这些成员变量也可以不写
         int _age;
         double _weight;
         NSString *_name;
     }
        
     // @property:可以自动生成某个成员变量的setter和getter声明
     @property int age;
     //- (void)setAge:(int)age;
     //- (int)age;
        
     /*
      @property int _age;
      //- (void)set_age:(int)_age;
      //- (int)_age;
      */
        
     @property int height;
     - (void)test;
     @property double weight;
     @property NSString *name;
     @end
        
     #import "Person.h"
        
     @implementation Person
        
     /*
      1.这句话的作用: @synthesize自动生成age的setter和getter实现,并且会访问_age这个成员变量,如果不存在,就会自动生成@private类型的_age成员变量
     2. @synthesize age生成怎样的get方法/set方法
     3. = _age 这个set/get方法中访问哪个成员变量
     4. 注意:如果只写@synthesize age,默认会访问age这个成员变量,如果没有age,就会自动生成@private类型的age变量
      5. 等价如下:
      -(void)setAge:(int)age{
      _age = age;
      }
      -(int)age{
      return _age;
      }
      6. 手动实现:
      若手动实现了setter方法,编译器就只会自动生成getter方法
      若手动实现了getter方法,编译器就只会自动生成setter方法
      若手动实现了setter方法/getter方法,编译器就不会自动生成不存在的成员变量
      */
     @synthesize age = _age;
        
     @synthesize height = _height;
        
     @synthesize weight = _weight, name = _name;
     -(void)test{
         NSLog(@"%zd",_height);
     }
     @end
    
  3. Xcode4.4之后
    只需要写@property int age;就可以了,会做以下几件事:
    1. 生成get/set方法的声明
    2. 生成带下划线的成员变量_age(注意这变量是@private)
    3. 生成get/set方法的实现
  4. @dynamic age;
    1. 告诉编译器,属性age的setter与getter方法由用户自己实现,不需要自动生成,或者,getter和setter方法在别处生成了(比如父类中),并没有在本类生成。
    2. 存取方法在运行时动态创建与绑定:主要使用在CoreData的实现NSManagerObject子类时使用

id

  1. id是一种数据类型
  2. 万能指针,能指向\操作任何OC对象id == NSObject *
  3. id是没有*的!!!
  4. id类型不能使用点语法!!!

构造方法

  1. +new方法
    1. 该方法是一个类方法
    2. 通过该方法可以创建一个对象
    3. 该方法实际上做了两件事
      1. 调用+alloc分配存储空间
      2. 在调用-init进行初始化
    4. 完整地创建一个可用的对象
      1. 分配存储空间 +alloc
      2. 初始化 -init
      // Person *p = [Person new];
      // 1.调用+alloc分配存储空间
     // Person *p1 = [Person alloc];
     // 2.调用-init进行初始化
     // Person *p2 = [p1 init];
    
  2. 构造方法
    1. 构造方法就是这个-init方法
    2. 作用:用来初始化对象,是个对象方法,-开头
  3. 重写构造方法
    1. 对象初始化完毕,默认成员变量都是0
    2. 重写构造方法的目的:为了让对象创建出来,成员变量就会有一些固定的值
    3. 在对象相应的类中,重写父类的构造方法(-init)
    4. 重写构造方法的注意点:
      1. 先调用父类的构造方法([super init]
      2. 再进行子类内部成员变量的初始化
       #import "Person.h"
       @implementation Person
              
       // 重写-init方法
       //- (id)init
       //{
       //    // 1.一定要调用回super的init方法:初始化父类中声明的一些成员变量和其他属性
             //  只有父类先初始化成功,才有必要进行下一步的初始化
       //    self = [super init]; // 当前对象 self
       //此时的super是NSObject,那NSObject的init做了什么事情呢?
       /*
       -(id)init{
           isa = [Person class];
           return self;
       }
       */
       //    // 2.如果对象初始化成功,才有必要进行接下来的初始化
       //    if (self != nil)
       //    { // 初始化成功
       //        _age = 10;
       //    }
       //    
       //    // 3.返回一个已经初始化完毕的对象
       //    return self;
       //}
       //简写如下:
       - (id)init {
           //注意之这里是=不是==!!!!
           if ( self = [super init] ) { // 初始化成功
               _age = 10;
           }
                  
           // 3.返回一个已经初始化完毕的对象
           return self;
       }
          
       @end
      
  4. 构造方法的执行过程

  5. 自定义构造方法
    1. 自定义构造方法的规范
      1. 一定是对象方法,一定以 - 开头
      2. 返回值一般是id类型
      3. 方法名一般以initWith开头(注意With必须首字母大写!!!)
    2. 代码举例:

       #import <Foundation/Foundation.h>
              
       @interface Person : NSObject
       @property NSString *name;
       @property int age;
       - (id)initWithName:(NSString *)name andAge:(int)age;
       @end
          
       #import "Person.h"
       @implementation Person
              
       - (id)initWithName:(NSString *)name andAge:(int)age
       {
           if ( self = [super init] )
           {
               _name = name;
               _age = age;
           }
           return self;
       }
       @end
       //创建一个Student子类
       #import "Person.h"
       @interface Student : Person
       @property int no;
       - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no;
       @end
              
       #import "Student.h"
       @implementation Student
       // 父类的属性交给父类方法去处理,子类方法处理子类自己的属性
       - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no
       {
           // 将name、age传递到父类方法中进行初始化
           if ( self = [super initWithName:name andAge:age])
           {
               _no = no;
           }
                  
           return self;
       }
       @end
              
       //调用:
       Student *p = [[Student alloc] initWithName:@"Jim" andAge:29 andNo:10];
      

分类(Category)

  1. 有一种要求:不改变原来的类,不创建新的类(继承),为原来的类添加方法.
  2. 分类的作用:在不改变原来类内容的基础上,可以为类增加一些方法
  3. 格式:

     //通常写在.h文件,但也可以都写在.m文件
     @interface Person (分类名称)
     //给原来类扩充的方法
     @end
     //.m文件
     @implementation Person (分类名称)
     //相应方法的实现
     //在这里可以访问原来类的成员变量
     //也可以重写原来类的方法
     @end
    
    
  4. 使用注意:
    1. 分类只能增加方法,不能增加成员变量。就算通过其他方式添加也并非本质的成员变量
    2. 分类方法实现中可以访问原来类中声明的成员变量(但注意这个成员变量一定不能是@private,即不是@property修饰的!!!)
    3. 分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用
    4. 方法调用的优先级:分类(最后参与编译的分类优先) –> 原来类 –> 父类
    5. 分类不一定要有.h文件,如果仅仅是重写、修改原来类中的方法,只需要在.m文件中重写就行了
  5. 代码举例:

     //Person.h文件
     #import <Foundation/Foundation.h>
     @interface Person : NSObject
     {
         //这样做事为了能够让这个成员变量在分类中也能够访问
         int _age;
     }
     @property int age;
     - (void)test;
     @end
     //Person.m文件
     #import "Person.h"
     @implementation Person
     - (void)test {
         NSLog(@"Person-test");
     }
     @end
        
     //Person+JJ.h文件
     #import "Person.h"
     @interface Person (JJ)
     - (void)test2;
     @end
        
     //Person+JJ.m文件
     #import "Person+JJ.h"
        
     @implementation Person (JJ)
     - (void)test2 {
         NSLog(@"-----test2");
     }
        
     - (void)test {
         NSLog(@"Person (JJ)-test");
     }
     @end
        
     //Person+MJ.h文件
     #import "Person.h"
        
     @interface Person (MJ)
     - (void)study;
     @end
     //Person+MJ.m文件
     #import "Person+MJ.h"
        
     @implementation Person (MJ)
     - (void)study {
         //访问原类的成员变量
         //_age一定不能是@private,即用@property修饰的不行
         NSLog(@"学习-----%d", _age);
     }
     //重写原类的方法
     - (void)test {
         NSLog(@"Person (MJ)-test");
     }
     @end
        
     //main.m文件
     #import <Foundation/Foundation.h>
     #import "Person.h"
     //先加载
     #import "Person+MJ.h"
     //后加载
     #import "Person+JJ.h"
        
     int main() {
         Person *p = [[Person alloc] init];
         //p.age = 10;
            
         // 优先去分类中查找,然后再去原来类中找,最后再去父类中找
         /*
          //先调用后加载的
          打印结果: Person (JJ)-test
          */
         [p test];
         [p study];
            
         return 0;
     }
    

类的本质

  1. 每个类在存储空间中只有一份,所有同类型的对象都指向这个类
  2. 类本身也是一个对象,是个Class(结构体指针,已经包含*了)类型的对象,简称类对象
  3. 实例对象:用一个类(类对象)创建的对象 类对象: 用一个类调用class创建出类对象
  4. 代码举例:

     void test() {
         // 利用Person这个类创建了2个Person类型的对象
         Person *p = [[Person alloc] init];
         Person *p2 = [[Person alloc] init];
         // 获取内存中的类对象
         //方法一:用实例对象获取,-class方法本质就是拿到该对象的isa指针(每个实例对象的isa指针指向他的类)
         Class c = [p class];
         Class c2 = [p2 class];
         // 方法二:利用类的Class,+class获取本身这个结构体指针
         Class c3 = [Person class];
            
         NSLog(@"c=%p, c2=%p, c3=%p", c, c2, c3);
     }
    

+load与+initialize

  1. +load
    1. 当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法(分类中最后掉调用)。只会调用一次。
    2. 先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法),先加载元原始类,再加载分类;
    3. 不管程序运行过程有没有用到这个类,都会调用+load加载
  2. +initialize
    1. 当第一次使用某个类时,就会调用当前类的+initialize方法,只会调用一次
    2. 先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)
    3. 分类中的+initialize方法会覆盖原类中的+initialize方法,而+load不会
  3. 在+initialize/+load方法中就没有必要在写[super …]了,因为他们一定会先调用父类的
  4. 代码举例:

     //父类person
     #import <Foundation/Foundation.h>
        
     @interface Person : NSObject
     @property int age;
     + (void)test;
     @end
        
     #import "Person.h"
     @implementation Person
     + (void)test
     {
         NSLog(@"调用了test方法");
     }
     // 当程序启动的时候,就会加载一次项目中所有的类。类加载完毕后就会调用+load方法
     + (void)load
     {
         NSLog(@"Person---load");
     }
     // 当第一次使用这个类的时候,就会调用一次+initialize方法
     + (void)initialize
     {
         NSLog(@"Person-initialize");
     }
     @end
        
     //子类Student
     #import <Foundation/Foundation.h>
     #import "Person.h"
     @interface Student : Person
     @end
        
     #import "Student.h"
     @implementation Student
     // 在类被加载的时候调用
     + (void)load
     {
         NSLog(@"Student---load");
     }
     + (void)initialize
     {
         NSLog(@"Student-initialize");
     }
     @end
        
     //分类
     #import "Person.h"
     @interface Person (MJ)
     @end
     #import "Person+MJ.h"
     @implementation Person (MJ)
     //分类最后加载
     + (void)load
     {
         NSLog(@"Person(MJ)---load");
     }
     //分类中的+initialize方法会覆盖原类中的+initialize方法
     + (void)initialize
     {
         NSLog(@"Person(MJ)-initialize");
     }
     @end
        
     //main.m
     #import <Foundation/Foundation.h>
     #import "Person.h"
     #import "Student.h"
     int main()
     {
          [[Student alloc] init];
            
         return 0;
     }
        
    
  5. 打印结果:

     2017-12-07 09:40:37.116124+0800 06-类的本质[18927:757753] Person---load
     2017-12-07 09:40:37.116692+0800 06-类的本质[18927:757753] Student---load
     2017-12-07 09:40:37.116760+0800 06-类的本质[18927:757753] Person(MJ)---load
     2017-12-07 09:40:37.116851+0800 06-类的本质[18927:757753] Person(MJ)-initialize
     2017-12-07 09:40:37.116967+0800 06-类的本质[18927:757753] Student-initialize
    

description

- description方法

默认情况下,利用NSLog和%@输出对象时,结果是:<类名:内存地址>,实际操作如下:

  1. 会调用对象p的-description方法
  2. 拿到-description方法的返回值(NSString *)显示到屏幕上
  3. -description方法默认返回的是“类名+内存地址”

+ description方法

默认情况下,利用NSLog和%@输出类对象时,结果是:<类名>,实际操作如下:

  1. 会调用类的+description方法
  2. 拿到+description方法的返回值(NSString *)显示到屏幕上
  3. +description方法默认返回的是“类名”

代码举例:

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property int age;
@property NSString *name;
@end

#import "Person.h"
@implementation Person
// 决定了实例对象的输出结果
- (NSString *)description
{
    // 下面代码会引发死循环
    // NSLog(@"%@", self);
    return [NSString stringWithFormat:@"age=%d, name=%@", _age, _name];
    //return @"3424324";
}

// 决定了类对象的输出结果
+ (NSString *)description
{
    return @"Abc";
}
@end

调用:
    Person *p = [[Person alloc] init];
    p.age = 20;
    p.name = @"Jack";
    NSLog(@"%@", p);
    
    Class c = [Person class];
    NSLog(@"%@", c);

NSLog

// 输出当前函数名
NSLog(@"%NSLog(@"%s\n", __func__);
// 输出行号
NSLog(@"%d", __LINE__);
// NSLog输出C语言字符串的时候,不能有中文
// NSLog(@"%s", __FILE__);
// 输出源文件的名称
printf("%s\n", __FILE__);

SEL

  1. 方法的存储位置
    1. 每个类的方法列表都存储在类对象中
    2. 每个方法都有一个与之对应的SEL类型的对象
    3. 根据一个SEL对象就可以找到方法的地址,进而调用方法
    4. SEL类型的定义
      typedef struct objc_selector *SEL;
  2. SEL对象的创建
    1. SEL s = @selector(test);
    2. SEL s2 = NSSelectorFromString(@"test");
  3. SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址。找到方法地址就可以调用方法
  4. [p test2];调用过程
    1. 把test2包装成SEL类型的数据
    2. 根据SEL数据找到对应的方法地址
    3. 根据方法地址调用对应的方法
  5. _cmd
    1. 每个方法内部都有个隐藏的参数_cmd
    2. _cmd代表着当前方法的SEL
    3. _cmd == @selecter(test2);
  6. 将SEL对象转为NSString对象
    NSString *str = NSStringFromSelector(@selector(test));

代码举例:


#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)test2;
- (void)test3:(NSString *)abc;
@end

#import "Person.h"
@implementation Person
- (void)test2
{
    //每个方法内部都有个隐藏的参数_cmd
    // _cmd代表着当前方法的SEL
    //_cmd == @selecter(test2);
    NSString *str = NSStringFromSelector(_cmd);
    
    // 会引发死循环
    // [self performSelector:_cmd];
    
    NSLog(@"调用了test2方法-----%@", str);
}
- (void)test3:(NSString *)abc
{
    NSLog(@"test3-----%@", abc);
}
@end

//调用:
    Person *p = [[Person alloc] init];
    
    //1. 间接调用test2方法
    //直接调用
    [p test2];
    //简介调用
    [p performSelector:@selector(test2)];
    
    //2.带参数的直接调用
    [p test3:@"123"];
    //间接调用
    SEL ss = @selector(test3:);
    [p performSelector:ss withObject:@"456"];
    
    //3.把一个字符串转化为sel
    NSString *name = @"test2";
    SEL s = NSSelectorFromString(name);
    [p performSelector:s];
    
Table of Contents