点语法
- 本质还是方法调用
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];
-
局限性
- 调用一个不存在的方法,编译器会马上报错
-
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顶部出现一道注释分割线,用来注释隔开某一组功能,更显眼








网友评论