一 OC中的私有方法
OC中的私有变量
- 实例变量(成员变量)既可以在@interface中定义,也可以在@implementation中定义;
- 写在@implementation中的成员变量,默认就是私有成员变量,在其他类中无法查看,也不能访问(只能在本类中访问)。
OC中的私有方法
- 私有方法:只有实现没有声明的方法
- 原则上:私有方法只能在本类中的方法的中才能调用
注意:在oc中没有真正的私有方法,因为oc是消息机制
二 @property基本概念
什么是@property
- @property是编译器的指令
@property会让编译器做什么呢?
- @property 用在声明文件中告诉编译器声明成员变量的的访问器(getter/setter)方法
- 这样的好处是免去我们手工书写getterr和setter方法繁琐的代码
@property基本使用
- 在@inteface中,用来自动生成setter和getter的声明
用@property int age;就可以代替下面的两行 - (int)age; // getter - (void)setAge:(int)age; // setter
- 在@inteface中,用来自动生成setter和getter的声明
@property编写步骤:
- 1.在@inteface和@end之间写上@property
- 2.在@property后面写上需要生成getter/setter方法声明的属性名称, 注意因为getter/setter方法名称中得属性不需要, 所以@property后的属性也不需要.并且@property和属性名称之间要用空格隔开
- 3.在@property和属性名字之间告诉需要生成的属性的数据类型, 注意两边都需要加上空格隔开
@property增强
- 默认情况下,setter和getter方法中的实现,会去访问下划线 _ 开头的成员变量。
- 如果没有会自动生成一个_开头的成员变量,自动生成的成员变量是私有变量, 声明在.m中,在其它文件中无法查看,但当可以在本类中查看
- @property只会生成最简单的getter/setter方法,而不会进行数据判断
- 如果需要对数据进行判断需要我们之间重写getter/setter方法
- 若手动实现了setter方法,编译器就只会自动生成getter方法
- 若手动实现了getter方法,编译器就只会自动生成setter方法
- 若同时手动实现了setter和getter方法,编译器就不会自动生成不存在的成员变量
如果利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量
@property修饰符
- 修饰是否生成getter方法的
- readonly 只生成setter方法,不生成getter方法
- readwrite 既生成getter 又生成setter方法(默认)
- 指定所生成的方法的方法名称
- getter=你定制的getter方法名称
- setter=你定义的setter方法名称(注意setter方法必须要有 :)
@property (getter=isMarried) BOOL married; // 通常BOOL类型的属性的getter方法要以is开头
- 修饰是否生成getter方法的
三 @synthesize基本概念
什么是@synthesize
- @synthesize是编译器的指令
@synthesize基本使用
- 在@implementation中, 用来自动生成setter和getter的实现
用@synthesize age = _age;就可以代替 - (int)age{ return _age; } - (void)setAge:(int)age{ _age = age; }
- 在@implementation中, 用来自动生成setter和getter的实现
@synthesize编写步骤
- 1.在@implementation和@end之间写上@synthesize
- 2.在@synthesize后面写上和@property中一样的属性名称, 这样@synthesize就会将@property生成的属性名称拷贝到@implementation中
- 3.由于getter/setter方法实现是要将传入的形参给属性和获取属性的值,所以在@synthesize的属性后面写上要将传入的值赋值给谁和要返回哪个属性的值, 并用等号连接
@synthesize注意点
@synthesize age = _age;
- setter和getter实现中会访问成员变量_age
- 如果成员变量_age不存在,就会自动生成一个@private的成员变量_age
@synthesize age;
- setter和getter实现中会访问@synthesize后同名成员变量age
- 如果成员变量age不存在,就会自动生成一个@private的成员变量age
多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接
@synthesize age = _age, number = _number, name = _name;
四 id类型
id是一个数据类型,是一个动态数据类型
静态数据类型的特点:
- 在编译时就知道变量的类型,
- 知道变量中有哪些属性和方法
- 在编译的时候就可以访问这些属性和方法,
- 如果是通过静态数据类型定义变量, 如果访问了不属于静态数据类型的属性和方法, 那么编译器就会报错
动态数据类型的特点:
- 在编译的时候编译器并不知道变量的真实类型, 只有在运行的时候才知道它的真实类型
- 如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错
id == NSObject * 万能指针
id和NSObject *的区别: NSObject *是一个静态数据类型,id是一个动态数据类型;
通过动态数据类型定义变量,可以调用子类特有的方法,可以调用私有方法
弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
为了避免动态数据类型引发的运行时错误,一般情况使用动态数据类型定义一个变量,在调用这个变量的方法之前在进行一次判断,判断当前变量是否能够调用这个方法
- - (BOOL)isKindOfClass:classObj 判断实例对象是否是这个类或者这个类的子类的实例
- - (BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
- + (BOOL) isSubclassOfClass:classObj 判断类是否是指定类的子类)
五 new方法实现原理
new方法实现原理
- 完整的创建一个可用的对象:Person *p=[Person new];new方法的内部会分别调用两个方法来完成3件事情:
- (1)使用alloc方法来分配存储空间(返回分配的对象);
- (2)使用init方法来对对象进行初始化。
- (3)返回对象的首地址
- 完整的创建一个可用的对象:Person *p=[Person new];new方法的内部会分别调用两个方法来完成3件事情:
可以把new方法拆开如下:
- (1).调用类方法+alloc分配存储空间,返回未经初始化的对象
Person *p1=[person alloc];
- (2).调用对象方法-init进行初始化,返回对象本身
Person *p2=[p1 init];
- (3).以上两个过程整合为一句:
Person *p=[[Person alloc] init];
- (1).调用类方法+alloc分配存储空间,返回未经初始化的对象
说明:
alloc 与 init合起来称为构造方法,表示构造一个对象
alloc 方法为对象分配存储空间,并将所分配这一块区域全部清0
init方法是初始化方法(构造方法),用来对象成员变量进行初始化,默认实现是一个空方法。
- 所以下面两句的作用是等价的
Person *p1 = [Person new];
Person *p = [[Person alloc] init];
iOS 程序通常使用[[类名 alloc] init] 的方式创建对象,因为这个可以与其他initWithXX:…的初始化方法,统一来。
六 构造方法
构造方法的用途
- 用于初始化一个对象,让某个对象一创建出来就拥有某些属性和值
重写init方法
-
- 先初始化父类,在初始化子类;
self = [super init];
- 先初始化父类,在初始化子类;
-
判断父类是否初始化成功,只有父类初始化成功才能继续初始化子类;
if(self != nil) { // (“ != nil”可以省略)--常写格式:if (self = [super init]) 初始化子类,设置属性值 }
返回当前对象的地址;
return self;
-
- (id)init {
self = [super init];
if (self) {
// Initialize self.
}
return self;
}
在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。
- 原则:先初始化父类的,再初始化子类的。
- 先调用父类的构造方法[super init];
- 再进行子类内部成员变量的初始化。
- 重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。
- 原则:先初始化父类的,再初始化子类的。
instancetype的作用
- instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。
注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id
七 自定义构造方法
自定义构造方法的规范
- (1)一定是对象方法,以减号开头
- (2)返回值一般是instancetype类型
- (3)方法名必须以initWith开头
代码示例
@interface Person : NSObject
@property int age;
@property NSString *name;
// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
- (id)initWithAge:(int)age;
- (id)initWithName:(NSString *)name;
- (id)initWithAge:(int)age andName:(NSString *)name; // 多个参数
@end
继承中的自定义构造方法
不能在子类访问父类私有变量
@interface Person : NSObject @property int age; - (id)initWithAge:(int)age; @end @interface Student : Person @property NSString *name; - (id)initWithAge:(int)age andName:(NSString *)name; @end @implementation Student - (id)initWithAge:(int)age andName:(NSString *)name { if (self = [super init]) { // 这个_Age是父类中通过property自动在.m中生成的无法继承,不能直接访问 // _age = age; [self setAge:age]; _name = name; } return self; } @end
父类的属性交给父类的方法来处理
@interface Student : Person @property NSString *name; - (id)initWithAge:(int)age andName:(NSString *)name; @end @implementation Student - (id)initWithAge:(int)age andName:(NSString *)name { if (self = [super initWithAge:age]) { _name = name; } return self; } @end
八 自定义类工厂方法
什么是工厂方法(快速创建方法)
- 类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。
自定义类工厂方法的规范
- (1)一定是+号开头
- (2)返回值一般是instancetype类型
- (3)方法名称以类名开头,首字母小写
子父类中的类工厂方法
- 由于子类默认会继承父类所有的方法和属性, 所以类工厂方法也会被继承
- 由于父类的类工厂方法创建实例对象时是使用父类的类创建的, 所以如果子类调用父类的类工厂方法创建实例对象,创建出来的还是父类的实例对象
- 为了解决这个问题, 以后在自定义类工厂时候不要利用父类创建实例对象, 改为使用self创建, 因为self谁调用当前方法self就是谁
代码案例
@interface Person : NSObject
+ (id)person;
@end
@implementation Person
+ (id)person
{
// return [[Person alloc]init];
// 谁调用这个方法,self就代表谁
// 注意:以后写类方法创建初始化对象,写self不要直接写类名
return [[self alloc]init];
}
@end
@interface Student : Person
@property NSString *name;
@end
@implementation Student
@end
int main(int argc, const char * argv[])
{
Student *stu = [Student person];// [[Student alloc] init]
[stu setName:@"xxx"];
}
九 类的本质
- 类其实也是一个对象,这个对象会在这个类第一次被使用的时候创建;
- 只要有了类对象,将来就可以通过类对象来创建实例对象;
实例对象中有一个isa指针, 指向创建自己的类对象;
如何获取类对象
- 通过实例对象
格式:[实例对象 class ]; 如: [dog class];
- 通过类名获取(类名其实就是类对象)
格式:[类名 class]; 如:[Dog class]
- OC实例对象类对象元数据之间关系
十 类的启动过程
+load方法
- 只要程序启动就会将所有类的代码加载到内存中, 放到代码区
- load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次
- 如果存在继承关系, 会先调用父类的load方法, 再调用子类的load方法
+initialize方法
- 当前类第一次被使用的时候就会调用(创建类对象的时候)
- initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次
- initialize用于对某一个类进行一次性的初始化
注意:只要类一启动,就会调用load方法,如果用到类,就会调用initialize方法,2个方法都仅会调用一次,initialize和load一样,如果存在继承关系,会先调用父类的initialize再调用子类的initialize。
十一 SEL类型
- 什么是SEL类型
- SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
SEL类型的定义:typedef struct objc_selector *SEL;
SEL使用
- 1.SEL类型的第一个作用, 配合对象/类来检查对象/类中有没有实现某一个方法
SEL sel = @selector(setAge:); Person *p = [Person new]; // 判断p对象中有没有实现-号开头的setAge:方法 // 如果P对象实现了setAge:方法那么就会返回YES // 如果P对象没有实现setAge:方法那么就会返回NO BOOL flag = [p respondsToSelector:sel]; NSLog(@"flag = %i", flag);
// respondsToSelector注意点: // 如果是通过一个对象来调用该方法,那么会判断该对象有没有实现-号开头的方法 // 如果是通过类来调用该方法, 那么会判断该类有没有实现+号开头的方法 SEL sel1 = @selector(test); flag = [p respondsToSelector:sel1]; NSLog(@"flag = %i", flag); flag = [Person respondsToSelector:sel1]; NSLog(@"flag = %i", flag);
2.SEL类型的第二个作用, 配合对象/类来调用某一个SEL方法
SEL sel = @selector(demo); Person *p = [Person new]; // 调用p对用中sel类型对应的方法 [p performSelector:sel]; SEL sel1 = @selector(signalWithNumber:); // withObject: 需要传递的参数 // 注意: 如果通过performSelector调用有参数的方法, 那么参数必须是对象类型, // 也就是说方法的形参必须接受的是一个对象, 因为withObject只能传递一个对象 [p performSelector:sel1 withObject:@"13838383438"]; // 注意:performSelector最多只能传递2个参数 SEL sel2 = @selector(sendMessageWithNumber:andContetn:); [p performSelector:sel2 withObject:@"138383438" withObject:@"abcdefg"];
3.配合对象将SEL类型作为方法的形参
@implementation Person - (void)makeObject:(id) obj performSelector:(SEL) selector { [obj performSelector:selector]; } @end int main(int argc, const char * argv[]) { Person *p = [Person new]; SEL s1 = @selector(eat); Dog *d = [Dog new]; [p makeObject:d performSelector:s1]; return 0; }
- 1.SEL类型的第一个作用, 配合对象/类来检查对象/类中有没有实现某一个方法