OC核心语法

点语法

  • 本质还是方法调用 setter\getter 方法
  • 利用点语法替换 setter\getter 方法
  • 当使用点语法时,编译器会自动展开成相应的方法
// 方法调用
Student *stu = [Student new];
[stu setAge: 10];
int age = [stu age];

// 点语法
stu.age = 10;
int age = stu.age
点语法.jpg
  • 注意点
- (int)June;
{
  return _june;

// 这样会引起死循环
// 因为这个点语法还是调用 get 方法,等于重复进当前方法
// return self.june;
}

- (void)setJune:(int)june
{
  _june = june;

// 这样会引起死循环
// 因为这个点语法还是调用 setJune 方法,等于重复进当前方法
// self.june = june
}

成员变量的 四种作用域

  • 局部变量、全局变量都有自己的作用域,成员变量也有
    • @private : 只能在当前类的对象方法中直接访问(@implementation 中默认是@private
    • @protected : 可以在当前类以及子类的对象方法中直接访问(@interface中默认是@protected
    • @public : 任何地方都可以直接访问对象的成员变量
    • @package : 只要处在同一体系内(框架)中就能直接访问对象的成员变量,介于 @private@public 之间
@interface VampireJune : NSObject
{
  @public // 在`任何`地方都能直接访问对象的成员变量
  int _age;

  @private // 只能在`当前类`的对象方法中直接访问
  int _height;

  @protected // 可以在`当前类以及子类`的对象方法中直接访问
  int _weight;
}
  • @implementation 中创建的成员变量,默认是私有的@private
  • @implementation不能定义和 @interface同名的成员变量

@property

  • 用在@interface
  • 用来自动生成某个成员变量的 setter\getter 方法声明
@interface VampireJune : NSObject
{
   int age; // 成员变量
}

 用 `@property int age;` 就可以代替下面的两行

- (void)setAge:(int)age; // setter
- (int)age; // getter

@end

  • 新特性
    • Xcode 4.4 后 @property就独揽了 @synthesize 的功能

    • 也就是说,@property 可以同时生成 setter\getter 方法的 声明和实现

    • 默认情况下,setter\getter 方法中的实现,会去访问下划线 _ 开头的成员变量

@synthesize

  • 用在@implementation
  • 用来自动生成某个成员变量的 setter\getter 方法实现
@implementation VampireJune

 用 `@synthesize age = _age;` 就可以代替下面的

- (void)setAge:(int)age
{
   _age = age;
}

- (int)age
{
  return age;
}

@end
  • 注意细节

  • @synthesize age = _age;

    • setter\getter 实现中会访问成员变量 _age
    • 如果成员变量 _age 不存在,就会自动生成 @private私有的成员变量 _age
  • @synthesize age;

    • 如果成员变量 age 不存在,就会自动生成 @private私有的成员变量 age
  • 手动实现

    • 若手动实现了 setter 方法,编译器就只会自动生成 getter 方法
    • 若手动实现了 getter 方法,编译器就只会自动生成 setter 方法
    • 同时手动实现了 setter\getter 方法,编译器 就不会 自动生成 不存在的 成员变量

id 类型 关键字

  • 万能指针,能指向\操作任何 OC对象,相当于 NSObject*
  • id 类型的定义
  • 注意: id 后面不要加上 *
typedef struct objc_object *id;

id v = [VampireJune new];

  • 局限性

    • 调用一个不存在的方法,编译器会马上报错
  • idinstancetype 的区别

    • instancetype 只能作为函数或者方法的返回值
    • id 能作为方法或者函数的返回值、参数类型,也能用来定义变量
  • instancetype 对比 id 的好处

    • 能精确的限制返回值的具体类型

完整的创建一个可用的对象

+(id)new 类方法

 1.分配存储空间    +alloc 
 2.初始化        -init

 1. 调用 `+alloc` 分配存储空间,返回一个不可用的类名类型的对象

例 类名是VampireJune 
   VampireJune *v1 = [VampireJune alloc];

 2. 再用 `v1` 调用 `-init` 进行初始化,返回一个可用的初始化好的对象
   VampireJune *v2 = [v1 init];

 综合两句写法 VampireJune *v3 = [[VampireJune alloc] init];

构造方法

  • -init 方法 : 用来初始化对象的方法,是个 对象方法

  • 重写 -init 方法

- (id)init
{
    条件
    1. 一定要调用回`super`的`init`方法:初始化父类中声明的成员变量和其他属性
    self = [super init]; // 当前对象 self

    2.如果对象初始化成功,才有必要进行接下来的初始化
    if(self != nil)
    {// 初始化成功
        操作成员变量
    }
    
    3.返回一个已经初始化完毕的对象
    return self;
}

    1,2步可以简化为
    if(self = [super init])
    {
        操作成员变量
    }
  • 重写构造方法的目的

    • 为了让对象创建出来,成员变量就会有一些固定的值
  • 注意点

    • 先调用父类的构造方法[super init]
    • 再进行子类内部成员变量的初始化
- (id)init
{
    if(self = [super init])
    {
        操作成员变量
    }
    return self;
}

自定义构造方法的规范

  • 一定是对象方法,一定以 - 开头

  • 返回值一般是 id 类型

  • 方法名一般以 initWith 开头

    • 父类的属性交给父类的方法去处理,子类方法处理子类自己的属性
  • 注:注:

    • 自定义构造方法,返回值处,不再写 id,写 instancetype,它好处是,在编译时,就能识别出 返回值 到底 给 哪个类去接收,如果接收类型与构造方法内部的类型不一致,立马报出警告,避免在程序运行的时候崩溃,而难以找到问题所在
- (instancetype)init
{
    if(self = [super init])
    {
        操作成员变量
    }
    return self;
}

Category 分类 类别 类目

  • 文件名会变成 类名+分类名称.h类名+分类名称.m
  • 作用
    • 不改变原来类内容的基础上,可以为增加一些方法
// 声明
@interface 类名(分类名称)
@end

// 实现
@implementation 类名(分类名称)
@end
  • 使用注意
    • 分类 只能增加方法,不能增加成员变量(如果想添加变量,可以考虑通过集成创建子类)
    • 分类方法实现中,可以访问原来类中声明的成员变量
    • 分类可以重新实现原来类中的方法,但是会覆盖原来方法
    • 方法调用的优先级:分类(多个分类如果实现了相同的方法,最后参与编译的分类优先) --> 原来类 --> 父类
    • 分类可以定义在单独 .h .m 文件中,也可以定义在原来类中
      • 一般情况下,都是定义在单独文件
      • 定义在原来类中的分类,只要求能看懂语法

Extension 类扩展

  • 又叫匿名分类没有名字分类
  • 写在.m文件中
  • 可以为某个类扩充一些 私有的 成员变量方法
  • 其他使用此类.h文件时,不能访问这些 私有的 成员变量方法
@interface 类名 ()
@end

@implementation 类名
@end

类的本质

  • 类本身也是一个对象,是个 Class 类型的对象,简称类对象
  • 类名就代表着类对象,每个类只有一个类对象
  • Class 类型的定义
typedef struct objc_class *Class;
  • 获取类对象2种方式
获取内存中的类对象 
Class c = [实例对象 class]; // 对象方法
Class c = [类名 class]; // 类方法

// 类对象调用类方法
类名 *实例对象名 = [c new];
  • 类的加载和初始化
    • +load

      • 在程序启动的时候,会加载项目中所有的类和分类,并调用所有类和分类的 +load 方法,只会调用一次
      • 先加载父类,再加载子类(先调用父类+load 方法,再调用子类+load 方法)
      • 先加载原始类,再加载分类
      • 不管程序运行过程中有没有用到这个类,都会调用+load加载
    • +initialize

      • 在第一次使用某个类时(比如创建类对象等),就会调用一次 +initialize 方法
      • 一个类只会调用一次+initialize方法
      • 先初始化父类,再初始化子类(先调用父类+initialize 方法,再调用子类+initialize 方法)
      • 当有分类时,第一次使用类时,只会调用分类的这个方法】
      • 可以监听第一次使用此方法

description

  • -description 对象方法
    • 使用 NSLog%@ 输出某个对象时,会调用 对象-description 对象方法,并拿到返回值进行输出
    • 决定了实例对象的输出结果
    • 会调用对象-description 对象方法
    • 拿到 -description 对象方法 的返回值(NSString *)显示到屏幕上
    • -description 对象方法 默认返回的是 <类名:内存地址>
 VampireJune *p = [[VampireJune alloc] init];  
 // 会调用对象的 `-description` 对象方法
 NSLog("%@",p);   --> 

// 决定了`实例对象`的输出结果
- (NSString *) description;
  • +description 类方法
    • 使用 NSLog%@ 输出某个类对象时,会调用 类对象+description 类方法,并拿到返回值进行输出
    • 决定了类对象的输出结果
    • 会调用+description 类方法
    • 拿到 +description 类方法 的返回值(NSString *)显示到屏幕上
    • +description 类方法 默认返回的是 类名
 Class c = [VampireJune class]  
 // 会调用类对象(也就是VampireJune类的)的 `+description` 类方法
 NSLog("%@",c);   --> VampireJune

// 决定了`类对象`的输出结果
+ (NSString *) description;
  • 重写 NSLog 默认输出

    • 重写 -description 对象方法 或者 +description 类方法即可
  • 死循环陷阱

    • 如果在 -description 对象方法中 使用 NSLog 打印 self

NSLog 输出

VampireJune *v = [[VampireJune alloc] init];

 // 指针变量的地址
 NSLog("%p",&v);

 // 对象的地址
 NSLog("%p",v);

 // <类名:对象地址>
 NSLog("%@",v);

 // 输出当前函数名
 NSLog("%s",__func__);

 // 输出行号
 NSLog("%d",__LINE__);

 // 输出源文件的名称
 printf("%s",__FILE__);
  • 缺点
    • NSLog 输出C语言字符串时,不能有中文(此时用 printf

SEL

  • 是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址,找到方法地址就可以调用方法

  • 每个 方法 列表都存储在 类对象

  • 每个 方法 都有一个与之对应的 SEL 类型的对象

  • 根据一个 SEL 对象就可以找到方法的地址,进而调用方法

  • 其实消息就是 SEL

  • SEL 类型的定义

typedef struct objc_selector *SEL;
  • test 包装成 SEL 类型的数据
  • 根据 SEL 数据找到对应的 方法地址
  • 根据 方法地址 调用对应的 方法
VampireJune *v = [[VampireJune alloc] init];
- (void)june;

 [v june];
 
 // 间接调用 june 方法
 [v performSelector:@selector(june)];
  • SEL 对象的创建
  • 把一个 字符串 转成 SEL 的数据,调用方法
NSString *name = @"VampireJune";

 SEL s = NSSelectorFormString(name);

 [v performSelector:s];
  • 需要参数的 方法 调用,方法名 必须写 冒号 :
- (void)vampire:(NSString *)june;

 SEL s = @selector(vampire:) 
 [v performSelector:s withObject:@"VampireJune"];
  • _cmd 代表着当前方法(每个方法中内部都有个_cmd
- (void)vampire
{
  NSString *str = NSStringFormSelector(_cmd);
  NSLog("调用了vampire方法------%@", str); 
  // 打印结果 - vampire
  
  // 会引发死循环
  // [self performSelector:_cmd];
}

OC编程小技巧

  • 只有利用 类名 调用类方法时, 需要在类名后面写 *

  • 其他情况下,类名后面统一加上一个 *

  • 返回值是 BOOL 类型的方法,方法名一般都以 is 开头

    • - (BOOL)isInteractWithOther:(Circle *)circle
  • 想要拥有某个 对象,就先创建 对象,然后调用 set 方法将对象传递给内部的 成员变量

  • 定义一个 2 个文件:.h 声明文件 .m 实现文件

    • .h :成员变量、方法的声明
    • .m :方法的实现
  • Xcode 特有的 注释 快速定位某段代码位置

    • #pragma mark - 快速定位某段代码位置
    • 上面的 - 写上后,会在 Xcode 顶部出现一道注释分割线,用来注释隔开某一组功能,更显眼

你可能感兴趣的:(OC核心语法)