点语法
- 本质还是方法调用
setter\getter
方法 - 利用
点语法
替换setter\getter
方法 - 当使用
点语法
时,编译器
会自动展开成相应的方法
// 方法调用
Student *stu = [Student new];
[stu setAge: 10];
int age = [stu age];
// 点语法
stu.age = 10;
int age = stu.age
- 注意点
- (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];
-
局限性
- 调用一个不存在的方法,编译器会马上报错
-
id
和instancetype
的区别-
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); --> <VampireJune:内存地址>
// 决定了`实例对象`的输出结果
- (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
顶部出现一道注释分割线,用来注释隔开某一组功能,更显眼