self关键字
self关键字离不开类,离开类之后就没有任何意义。
如果self在对象方法中,那么self就代表调用当前对象方法的那个对象。
如果self在类方法中,那么self就代表调用当前类方法的那个类。
self关键字不仅仅可以调用类方法,还可以调用对象方法。格式如下:
[self 方法名];
注意点:
不能在对象方法或者类方法中利用self调用当前self所在的方法,或者说不能在description方法当中调用self,否则都会形成死循环。
self会自动区分类方法和对象方法,如果在类方法中使用self调用对象方法,或者在对象方法中调用类方法,都会直接报错。
修饰符
@public:可以其它类中访问被public修饰的成员变量,也可以在本类中访问被public修饰的成员变量,可,也可以在子类中访问父类中被public修饰的成员变量。
@private:不可以在其它类中访问被private修饰的成员变量,可以在本类中访问被private修饰的成员变量,也不可以在子类中访问父类中被private修饰的成员变量。
@protected:不可以在其它类中访问被protected修饰的成员变量,可以在本类中访问被protected修饰的成员变量,也可以在子类中访问父类中被protected修饰的成员变量。
注意:默认情况下,所有没有用修饰符修饰的变量都是用protected修饰的。
@package:介于public和private之间的,如果是在其它包中访问那么就是private的,如果是在当前代码所在的包中访问就是public的。
oc中的私有变量和私有方法
私有变量
变量既可以在@interface中定义,也可以在@implementation中定义,写在@implementation中的成员变量。
默认就是私有成员变量,并且和利用@private修饰的不太一样,在@implementation中定义的成员变量在其它类中无法查看,也无法访问。
在@implementation中定义的私有变量只能在本类中访问。
私有方法
如果只有方法的实现,没有方法的声明,那么该方法就是私有方法,但在oc中没有真正的私有方法,因为OC是消息机制。
property基本使用
@property是一个编译器指令,在xcode4.4之前,可以使用@property来代替getter和setter方法的声明。也就是说只需要写上@property就不用写getter和setter。格式如下:
//变量名称不要带下横线
@property 类型 变量名称
//定义成员变量
@public NSString *_name;
@property NSString *name;
//等价于
- (void) setName:(NSString *)name;//setter方法
- (NSString *)name;//getter方法
在xcode4.4之后,苹果对@property进行了一个增强,只要利用一个@property就可以同时生成setter和getter方法的声明和实现。
弊端:只会生成最简单的getter和setter方法的声明和实现,并不会对传入的数据进行过滤
如果想对传入的数据进行过滤,那么就必须重写getter和setter方法的实现。
如果不想对传入的数据进行过滤,仅仅是提供一个方法给外界操作成员变量,那么就可以使用@property。
如果利用@property来生成getter和setter方法,可以不写成员变量,系统会自动给我们生成一个"_"开头的成员变量
注意:
@property自动生成的成员变量是一个私有的成员变量,也就是说是在.m文件中生成的,而不是在.h文件中生成的。
如果重写了setter方法,那么@property就只会生成getter方法,反过来则一致。
如果同时写了getter和setter方法,那么@property就不会自动生成私有的成员变量。
@property修饰符
只读修饰符:(readonly),代表只生成getter方法不生成setter方法。
可读可写修饰符:(readwrite),代表即生成getter方法,也生成setter方法,默认情况下@property的修饰符就是readwrite。
修改getter方法名(getter=新的方法名)
修改setter方法名(setter=新的方法名:参数)
格式如下:
@property(属性修饰符) 数据类型 变量名称;
@property(readonly) NSString *name;//只读
@property(readwrite) NSString *name;//可读可写
@property(getter=huoqu) NSString *name;//定义getter方法名为huoqu
@property(setter=shezhi:@"张三") NSString *name;//定义setter方法名为shezhi并传递“张三”参数
synthesize基本使用
@synthesize也是一个编译器指令,它可以简化getter和setter方法的实现,在@synthesize后面告诉编译器,需要实现哪个@property生成的声明。具体格式如下:
//变量名称不要带下横线
@synthesize property定义的名称 对应的成员变量
@synthesize name = _name;
//等价于
- (void) setName:(NSString *)name{
_name = name;
}//setter方法
- (NSString *)name{
return _name;
}//getter方法
注意:在@synthesize最后面没有告诉系统将传入的值赋值给谁,系统会默认会赋值给和紧跟着@synthesize后面写的property定义的名称相同的成员变量。
动态数据类型(id数据类型)
默认情况下所有的数据类型都是静态数据类型。
静态数据类型的特定:
-
在编译时就知道变量的类型,且知道变量中有哪些属性和方法。
-
在编译的时候就可以访问这些属性和方法,且如果是通过静态数据类型定义变量,访问了不属于静态数据类型的属性和方法,那么编译器会报错。
-
通过静态数据类型定义的变量,不能调用子类特有的方法。
动态数据类型其实指的就是id数据类型,是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针。格式定义如下:
id 对象名称 = [类名 new];
//比如已经创建好了一个Student类
id stu = [Student new];
动态数据类型的特点:
-
在编译的时候编译器并不知道变量的真实类型,只有在运行的时候才知道它的真实类型。
-
如果通过动态数据类型定义的变量,访问了不属于动态数据类型的属性和方法,编译器不会报错。
-
通过动态数据类型定义的变量,可以调用子类特有的方法,也可以调用私有方法。
动态数据类型的弊端:
-
由于动态数据类型可以调用任意方法,所以有可能调用到不属于自己的方法,而编译时又不会报错,所以可能导致运行时的错误。
动态数据类型的应用场景:
-
多态,可以减少代码量,避免调用子类特有的方法需要强制类型转换。
-
为了避免动态数据类型引发运行时的错误,一般情况下如果使用动态数据类型定义一个变量,在调用这个变量的方法之前会进行一次判断,判断当前变量是否能够调用这个方法
//定义动态数据类型,创建一个学生对象
id obj = [Student new];
//判断指定的对象是否是某一个类,或者是某一个类的子类
if([obj isKindOfClass:[Student class]]){
//执行对象里边的方法
}
//判断指定的对象是否是当前指定的类实例
if([obj isMemberOfClass:[Student class]]){
//执行对象里边的方法
}
new和alloc的区别
new和alloc本质上没有任何区别,它们都可以用于创建对象,代码格式如下:
Person *p = [Person new];
//等价于
Person *p1 = [[Person alloc] init];
类的构造函数
类的构造函数定义是在.m文件中,也就是说,定义在@implementation和@end之间,每次创建对象的时候,首先会执行init也就是构造方法,具体格式如下:
- (instancetype)init
{
//调用父类的init构造函数,初始化成功后返回对应的地址,如果初始化失败就会返回nil
//nil == 0 == false == 没有初始化成功
self = [super init];
//判断父类是否初始化成功
if (self) {
NSLog(@"通常这里会对成员变量进行初始化赋值!");
}
//返回地址
return self;
}
自定义构造函数
自定义构造方法其实就是自定义一个init方法,换个角度来说,构造方法也是方法的一种,和其它的方法没什么区别,具体格式如下:
//声明成员变量
@public NSString *_name;
//声明自定义构造函数
- (instancetype)initWithName:(NSString *)name;
//实现自定义构造函数
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
自定义构造方法注意事项:
-
一个类可以有0个或者多个自定义构造方法
-
方法名称一定要以init开头,且后面With的W一定要大写。
-
返回值类型一定返回id/instancetype
-
一定是对象方法
id和instancetype的区别
它们两个都是动态数据类型,也就是instancetype == id == 万能指针 == 指向一个对象。
id在编译的时候不能判断对象的真实类型,instancetype在编译的时候可以判断对象的真实类型。
id可以用来定义变量,可以作为返回值,可以作为形参。
instancetype只能用于作为返回值,通常在定义构造函数init的时候使用。
注意:以后但凡自定义构造方法,返回值尽量使用instancetype,不要使用id。
类工厂方法基本概念
类工厂方法用于创建对象的类方法,称之为类工厂方法。类工厂方法中主要用于给对象分配存储空间和初始化这块存储空间。
自定义类工厂方法的规范:
-
一定是类方法,也就是以”+“符号开头的。
-
方法名称以类的名称开头,首字母小写。
-
一定有返回值,返回值是id/instancetype
自定义类工厂方法是苹果的一个规范,一般情况下,会给一个类提供自定义构造方法和自定义类工厂方法用于创建一个对象。
注意点:但凡自定义类工厂方法,在类工厂方法中创建对象一定不要使用类名来创建,一定要使用self来创建。 标准类工厂方法格式如下:
//声明类
@interface Person:NSObject
//声明属性
@property NSString *name;
//声明无参类工厂方法
+ (instancetype)person;
//声明带参类工厂方法
+ (instancetype)personWithName:(NSString *)name;
@end
//实现类
@implementation Person
//实现无参类工厂方法
+ (instancetype)person{
return [[self alloc] init];
}
//实现带参类工厂方法
+ (instancetype)personWithName:(NSString *)name{
Person *p = [[self alloc] init];
[p setName:name];
return p;
}
@end
//主程序入口
int main(int argc, const char * argv[]) {
//使用不带参数的类工厂方法创建实例对象
Person *p = [Person person];
p.name = @"李四";
NSLog(@"名字 = %@",p.name);
//使用带参数的类工厂方法创建实例对象
Person *p1 = [Person personWithName:@"张三"];
NSLog(@"名字 = %@",p1.name);
return 0;
}
类的启动过程
只要程序启动就会将类的代码加载到内存中,放到代码区,而oc中的类有一个load方法,它会在当前类被加载到内存的时候调用,且仅会调用一次,如果存在继承关系,会先调用父类的load方法,再调用子类的load方法,load方法它是定义在.m文件中,也就是@implementation和@end中,具体实现代码如下:
//声明类
@interface Person:NSObject
@end
//实现类
@implementation Person
//重写load方法
+ (void)load{
NSLog(@"Person load方法被执行!");
}
@end
//声明Student类
@interface Student:Person
@end
//实现Student类
@implementation Student
//重写load方法
+ (void)load{
NSLog(@"Student load方法被执行!");
}
@end
//注意:上面执行的时候会优先执行Person的Load方法然后再到Student的load方法。
oc中的类还有另外一个方法initialize方法,当当前类第一次被使用的时候就会调用,也就是创建类对象的时候,在整个程序的运行过程中只会被调用一次,无论这个类创建多少次都只会调用一次,经常用于对某一个类进行一次性的初始化,它和load方法一样,如果存在继承关系,会先调用父类的initialize方法再调用子类的initialize方法,具体代码定义如下:
//声明类
@interface Person:NSObject
@end
//实现类
@implementation Person
//重写initialize方法
+ (void)initialize
{
NSLog(@"Person initialize方法被执行!");
}
@end
//声明Student类
@interface Student:Person
@end
//实现Student类
@implementation Student
//重写initialize方法
+ (void)initialize
{
NSLog(@"Student initialize方法被执行!");
}
@end
//创建Student对象实例
Student *stu = [[Student alloc] init];
//注意:上面执行的时候会优先执行Person的initialize方法然后再到Student的initialize方法。