ios 基本语法介绍(上篇)
因为*近在学ios了,其实本来老早就开始学了,但一直没啥大的突破,知道*近我们的c++课上到多态哪里之后,再来看oc,突然就通了····不知道怎么回事····
下面是我在简书上看到的oc的基础语法。
objectve-C语法总结<1>
大纲
- 0.OC数据类型
- 1.声明一个类
- 2.实现一个类
- 3.创建一个对象
- 4.对象的注意点
- 5.对象方法
- 6.类方法
- 7.getter/setter方法
- 8.点语法
- 9.self关键字
- 10.OC中的继承
- 11.super关键字
- 12.OC中的多态
- 13.实例变量访问修饰符
- 14.私有变量和私有方法
- 15.@property概念
- 16.@property修饰符
- 17.id类型
- 18.new方法实现原理
- 19.构造方法
- 20.instancetype与id
- 21.类的启动过程
- 22.SEL介绍
- 23.@class使用
- 24.注释和指令
- 25.分类
- 26.Block
0.OC中的数据类型
- OC是增强了C的特性,所以在变量和基本数据类型上基本与C一致。
- 在OC中变量命名有如下规则:
- 由字母、数字、下划线、$符号组成
- 必须以字母、下划线、$符号开头
- 大小写敏感
- OC中有如下基本数据类型:
关键字 | 数据类型 |
---|---|
int: | 声明整型变量 |
double: | 声明双精度变量 |
float: | 声明浮点型变量 |
char: | 声明字符型变量 |
id: | 通用的指针类型 |
enum: | 声明枚举类型 |
long: | 声明长整型变量或函数 |
short: | 声明短整型变量或函数 |
signed: | 声明有符号类型变量 |
struct: | 声明结构体变量 |
union: | 声明共用体(联合)数据类型 |
unsigned: | 声明无符号类型变量 |
void: | 声明函数无返回值或无参 |
- 数据类型格式化字符:
数据类型 | oc关键字 | 格式说明引导符 |
---|---|---|
整型 | int | %d.%i |
短整型 | short int | %hd.%hi |
长整型 | long int | %ld.%li |
无符号短整型 | unsigned int | %u |
无短整型 | unsigned short | %hu |
无符号长整型 | unsigned long | %lu |
浮点型 | float | %f |
双精度型 | double | %f |
长双精度型 | long double | %lf |
字符型 | char | %c |
- 举例
-
-
-
int main(int argc, const char * argv[])
-
{
-
-
@autoreleasepool {
-
-
//保留字是不能定义为变量名的,例如下面的int、float等就不能作为变量名
-
int i = 2;
-
float f = 2.3f;
-
double d = 2.3e12;
-
char c = ‘a’;
-
-
//输出数据
-
NSLog(@”i : %d”,i);
-
NSLog(@”f : %f 截取后 : %.2f”,f,f);
-
NSLog(@”d : %e 截取后 : %.2e”,d,d);
-
NSLog(@”c : %c , %d”,c ,c );
-
-
//数据转换
-
//数据类型容量大的转成小的可能会丢失精度
-
int i2 = (int)f;
-
float f2 = (float)i ;
-
-
NSLog(@”数据转换”);
-
NSLog(@”i2 : %d”,i2);
-
NSLog(@”f2 : %f”,f2);
-
-
NSLog(@”变量的作用域”);
-
if(YES){
-
int i3 = 2;
-
NSLog(@”i3 : %d”,i3);
-
}
-
//在if的{}外面是无法访问到内部的i3变量的,因为i3变量的作用域就只是那对{}内部
-
//NSLog(@”i3 : %d”,i3);
-
/*
-
运行结果
-
[859:303] i : 2
-
[859:303] f : 2.300000 截取后 : 2.30
-
[859:303] d : 2.300000e+12 截取后 : 2.30e+12
-
[859:303] c : a , 97
-
[859:303] 数据转换
-
[859:303] i2 : 2
-
[859:303] f2 : 2.000000
-
*/
-
}
-
return 0;
-
}
1.声明一个类
- 格式
- 注意:
- 1.必须以@interface开头,@end结尾
- 2.成员变量的声明,必须写在@interface与@end之间的大括号中
- 3.方法的声明必须在{}下面,不能写在{}中
2.实现一个类
- 格式
-
@implementation MyClass
-
-
// 对象方法
-
– (id)initWithString:(NSString *)aName
-
{
-
//TODO
-
}
-
-
// 类方法
-
+ (MyClass *)myClassWithString:(NSString *)aName
-
{
-
//TODO
-
}
-
-
@end
- 注意:
- 1.必须以@implementation开头,@end结尾
- 2.类名必须和声明的一致
3.创建一个对象
- 用类的方式告诉计算机,我们需要一个什么样的对象,之后我们要在程序中使用这个对象,就必须先创建一个对象
- 注意[Car new];做了三件事
- 1.在堆内存中开辟了一块新的存储空间
- 2.初始化成员变量(写在类声明大括号中的属性就叫成员变量,也叫实例变量)
- 3.返回指针地址
- 消息机制
- 使用对象调用方法就是OC中的消息机制
- OC中调用方法的格式:[类名或者对象名 方法名];
4.对象的注意点
- 可以通过 对象->对象成员(注意声明属性为:@public) 的方式访问对象中的成员,
- 每一个对象中都有一份属于自己的属性。
- 对其中一个对象的成员进行了修改。和另一个对象没有关系
5.对象方法
5.1.对象方法的声明
- 格式
- 特征
- 对象方法以-开头如 -(void)xx;
- 对象方法只能由对象来调用
- 对象方法中可以访问当前对象的成员变量
- 调用格式 [对象名 对象方法名];
- 示例
- 在MyClass.h文件中声明
-
@interface MyClass : NSObject
-
-
//声明没有返回值的方法
-
– (void)method;
-
//声明有返回值的方法
-
– (int)newMethod;
-
//声明有返回值有参数的方法
-
– (int)method: (int)var;
-
//声明有返回值有多个参数的方法
-
– (int)method: (int)var1 andVar2: (int)var2;
-
-
@end
5.2.对象方法实现
- 在MyClass.m文件中实现
-
@implementation MyClass
-
-
//声明没有返回值的方法
-
– (void)method{
-
// TODO
-
}
-
//声明有返回值的方法
-
– (int)newMethod{
-
// TODO
-
return 10;
-
}
-
//声明有返回值有参数的方法
-
– (int)method: (int)var{
-
// TODO
-
return 20;
-
}
-
//声明有返回值有多个参数的方法
-
– (int)method: (int)var1 andVar2: (int)var2{
-
// TODO
-
return 30;
-
}
-
-
@end
- 必须以@implementation开头,@end之间
- 在声明的后面加上{}即表示实现
- 需要实现的代码写在{}中
6.类方法
6.1.类方法声明
- 格式
- 将对象方法-号变为+号
- 特征(很像java中静态方法)
- 类方法以+开头 如+(void)put;
- 类方法只能由类来调用
- 类方法中不能访问实例(成员)变量,因为类方法由类来调用,并没有创建存储空间 来存储类中的成员变量。
- 类方法的好处:
- 节省内存空间
- 不依赖于对象,执行效率更高;
- 能用类方法解决的问题,尽量使用类方法;
- 类方法的场合:
- 当方法内部不需要使用到成员变量时,可以改为类方法
- 类方法一般用于编写工具方法
- 示例
-
//声明没有返回值的方法
-
+ (void)method;
-
//声明有返回值的方法
-
+ (int) newMethod;
-
//声明有返回值有参数的方法
-
+ (int)method: (int)var;
-
//声明有返回值有多个参数的方法
-
+ (int)method: (int)var1 andVar2: (int)var2;
6.2.类方法实现
-
//声明没有返回值的方法
-
+ (void)method{
-
// TODO
-
}
-
//声明有返回值的方法
-
+ (int)newMethod{
-
// TODO
-
return 10;
-
}
-
//声明有返回值有参数的方法
-
+ (int)method: (int)var{
-
// TODO
-
return 20;
-
}
-
//声明有返回值有多个参数的方法
-
+ (int)method: (int)var1 andVar2: (int)var2{
-
// TODO
-
return 30;
-
}
- 必须卸载以@implementation开头,@end之间
- 在声明的后面加上{}即表示实现
- 将需要实现的代码写在{}中
6.3.对象方法和类方法区别
- 对象方法
- 对象方法是属于对象的
- 以减号-开头
- 只能让对象调用,没有对象,这个方法根本不可能被执行
- 对象方法能访问实例变量(成员变量)
- 对象方法中可以调用当前对象的对象方法
- 对象方法中可以调用其他对象的对象方法
- 对象方法中不可以调用类方法
- 类方法
- 类方法是属于类的
- 以加号+开头
- 只能用类名调用,对象不能调用
- 类方法中不能直接访问实例变量(成员变量)
- 类方法中不能直接调用对象方法,要想调用对象方法,必须创建或传入对象。
- 使用场合:
- 当不需要访问成员变量的时候,尽量用类方法
- 类方法和对象方法可以同名
7.getter/setter方法
- 代码实现和java基本一样
-
@interface MyClass()
-
{
-
// 定义一个NSString(字符串)类型的属性Name
-
NSString *Name;
-
}
-
@end
-
-
@implementation MyClass
-
-
// setter方法
-
– (void)setName:(NSString *)name{
-
Name = name;
-
}
-
-
// getter方法
-
– (NSString *)name{
-
return name;
-
}
-
@end
8.点语法
8.1点语法的本质
- 其实点语法的本质还是方法调用
- 当使用点语法时,编译器会自动展开成相应的方法
- 当点语法使用在 “=“赋值符号左侧的时候,点语法会被展开为setter方法的调用,其他情况(等号右侧、直接使用)为点语法展开为getter方法的调用
8.2点语法注意点
- 点语法的本质是方法的调用,而不是访问成员变量,当使用点语法时,编译器会自动展开成相应的方法调用。
- 点语法的本质是转换成相应的对setter和getter方法调用,如果没有set和get方法,则不能使用点语法。
- 不在要再getter 与 setter方法中使用本属性的点语法
-
– (void) setAge:(int)age {
-
// 下面的代码会引发死循环
-
self.age = age;
-
//编译器展开后 [self setAge:age]
-
}
-
-
-
– (int) age {
-
// 下面的代码会引发死循环
-
return self.age;
-
// 编译器展开后 [self age]
-
}
9.self关键字
- oc语言中的self,相当于java中的this关键字。
- 在类方法中的self,代表的是类对象,可以通过self调用该类对象的其他类方法。
- 在对象方法中self,代表的是该类的对象,可以通过self 关键字调用该对象的其他对象方法,同时也可以访问成员变量。
总结
- 谁调用self所在的方法,那么self就是谁
- self在类方法中就是这个类的类对象,全局只有一个,可通过self本类中的其他类方法,但是不能通过self来调用对象方法或访问成员变量
- self在对象方法中,就是调用这个方法的那个对象, 可以通过self调用其他本类中其他的对象方法,访问成员变量,但不能通过self调用本类的类方法。
- 通过self调用方法的格式:[self 方法名];
- 通过self访问成员变量格式:self->成员变量名
10.OC中的继承
10.1继承语法
- 在声明子类的时候,在子类名称后面通过:父类名称方式来实现继承。
和java一样,OC也不能多继承。
-
@interface 子类名称 : 父类名称
-
-
@end
10.2方法重写
- 在子类中实现与父类中同名的方法,称之为方法重写;
- 重写以后当给子类发送这个消息的时候,执行的时在子类中重写的那个方法,而不是父类中的方法。
- 如果在想在子类中调用被子类重写的父类的方法,可以通过super关键字
- 使用场景:当从父类继承的某个方法不适合子类,可以在子类中重写父类的这个方法。
-
– (void)viewDidLoad {
-
// 调用父类的viewDidLoad方法。
-
[super viewDidLoad];
-
// 在重写方法中做一些操作
-
NSLog(@”%@”,@”my first iOS project”);
-
-
}
11.super关键字
- 直接调用父类中的某个方法
- super在对象方法中,那么就会调用父类的对象方法
super在类方法中,那么就会调用父类的类方法
12.OC中的多态
- Animal是父类,子类有Cat 和 Dog,子类分别重写了父类中的eat方法;实例化对象的时候可以用下面的方法:
-
Animal *animal = nil;
-
-
//实例化猫的对象
-
animal = [Cat new];
-
[animal eat];
-
-
//实例化狗的对象
-
animal = [Dog new];
-
[animal eat];
13.实例变量访问修饰符
13.1.实例变量的作用域
@public
- 可以在其它类中访问被public修饰的成员变量
- 也可以在本类中访问被public修饰的成员变量
- 可以在子类中访问父类中被public修饰的成员变量
@private
- 不可以在其它类中访问被private修饰的成员变量
- 可以在本类中访问被private修饰的成员变量
- 不可以在子类中访问父类中被private修饰的成员变量
@protected
- 不可以在其它类中访问被protected修饰的成员变量
- 可以在本类中访问被protected修饰的成员变量
- 可以在子类中访问父类中被protected修饰的成员变量
注意: 默认情况下所有的实例变量都是protected
@package
- 介于public和private之间的
- 如果是在其它包中访问那么就是private的
- 如果是在当前代码所在的包种访问就是public的
-
@interface Person : NSObject
-
{
-
@public
-
int _age;
-
-
@private
-
double _height;
-
-
@protected// 默认就是protected
-
double _weight;
-
-
@package// 相当于public
-
NSString *_name;
-
NSString *_tel;
-
NSString *_email;
-
}
-
@end
14.私有变量和私有方法
- 在类的实现即.m文件中也可以声明成员变量,但是因为在其他文件中通常都只是包含头文件而不会包含实现文件,所以在.m文件中声明的成员变量是@private的。在.m中定义的成员变量不能和它的头文件.h中的成员变量同名。
-
@interface MyClass()
-
{
-
NSString *Name;
-
}
-
@end
-
-
@implementation MyClass
-
//在.h文件中没有声明,只在.m文件中实现就是私有方法
-
– (void)method{
-
-
// TODO
-
}
-
@end
15.@property概念
- @property是一个编译器指令。我*喜欢的一个功能之一。在java中类的每一个属性都有对应getter/setter方法。Eclipse等之类的IDE开发工具也集成了自动生成属性的getter/setter方法功能,但是还是需要程序员自己选中需要生成代码的属性然后生成。在oc中你只需要像这样一句话。So easy!
-
@interface Person : NSObject
-
-
@property int age;
-
-
@end
-
- 在使用@property定义属性之后,编译器都帮我们干了什么?
- Xcode4.4之前
编译器只要看到@property, 就知道我们要生产某一个属性的getter/setter方法的声明,相当于实现了如下的两个方法声明。-
– (void)setAge:(int)age;
-
– (int)age;
-
- Xcode4.4之后有爱的Apple对@property进行了一个增强。
1.利用@property可以同时自动生成setter/getter方法的声明和实现。
2.利用@property来生成getter/setter方法, 那么我们可以不写成员变量, 系统会自动给我们生成一个_开头的成员变量(@property自动帮我们生成的成员变量是一个私有的成员变量, 也就是说是在.m文件中生成的, 而不是在.h文件中生成的)-
//Person.h文件
-
@interface Person : NSObject
-
-
@property int age;
-
/*
-
// 相当于生成了如下的getter/setter方法的声明
-
– (void)setAge:(int)age;
-
– (int)age
-
*/
-
@end
-
-
//Person.m文件
-
-
@@implementation Person
-
-
@property int age;
-
/*
-
// 相当于生成了如下的getter/setter方法的实现
-
– (void)setAge:(int)age
-
{
-
//_age编译器自动帮我们生成的私有成员变量
-
_age = age;
-
}
-
– (int)age
-
{
-
return _age;
-
}
-
*/
-
@end
3.@property只会生成*简单的getter/setter方法,而不会进行数据判断。如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法。如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property。
- 如果重写了setter方法, 那么@property就只会生成getter方法
-
- (void)setAge:(int)age
-
{
-
//对传递过来的age进行过滤
-
if (age < 0) {
-
age = 0;
-
}
-
_age = age;
-
}
-
- 如果重写了getter方法, 那么@property就只会生成setter方法
-
– (int)age
-
{
-
return _age;
-
}
- 如果同时重写了getter/setter方法, 那么@property就不会自动帮我们生成私有的成员变量。
-
-
- Xcode4.4之前
16. @property修饰符
- 修饰是否生成getter方法的
- readonly 只生成setter方法,不生成getter方法
- readwrite 既生成getter 又生成setter方法(默认)
@property (readonly) int age;
- 指定所生成的方法的方法名称
- getter=你定制的getter方法名称
- setter=你定义的setter方法名称(注意setter方法必须要有 🙂
-
@property (getter=isMarried) BOOL married;
-
说明,通常BOOL类型的属性的getter方法要以is开头
-
- 多线程管理
- atomic :性能低(默认)
- nonatomic :性能高(iOS开发中都用这个属性)
@property (nonatomic) NSString *name;
- 控制set方法的内存管理
MRC(手动引用计数)- retain : release旧值,retain新值(用于OC对象)
- assign : 直接赋值,不做任何内存管理(默认,用于非OC对象类型)
- copy : release旧值,copy新值(一般用于NSString *)
ARC(自动引用计数)
- strong : 强引用,相当于MRC下的retain。用于OC对象。
- weak : 弱引用,相当于MRC下的assign。一般用来防止循环引用。
- copy : release旧值,copy新值(一般用于NSString *)
-
// MRC
-
@property (nonatomic, retain) User *user;
-
@property (nonatomic, assign) int age;
-
@property (nonatomic, copy) NSString *name;
-
-
// ARC
-
@property (nonatomic, strong) User *user;
-
@property (nonatomic, assign) int age;
-
@property (nonatomic, weak) UIView *view;
-
@property (nonatomic, copy) NSString *name;
-
-
说明,此处大概了解下内存管理,后面会详细的说明。
-
- 总结
17.id类型
17.1.静态类型和动态类型
- 静态类型
- 在编译的时候就知道这个指针变量所属的类,这个这个变量总是存储特定类的对象。
Person *p = [Person new];
- 在编译的时候就知道这个指针变量所属的类,这个这个变量总是存储特定类的对象。
- 动态类型
- 这一特性是程序直到执行时才确定对象所属的类
id obj = [Person new];
- 这一特性是程序直到执行时才确定对象所属的类
17.2.为什么要有动态类型?
- 我们知道NSObject是OC中的基类(相当于java中object)
- 那么任何对象的NSObject类型的指针可以指向任意对象,都没有问题
- 但是NSObject是静态类型,如果通过它直接调用NSObject上面不存在的方法,编译器会报错。
- 你如果想通过NSObject的指针调用特定对象的上面,就必须把NSObject * 这种类型强转成特定类型。然后调用。如下
-
//定义NSObject * 类型
-
NSObject* obj = [Cat new];
-
//需要强制类型转换
-
Cat *c = (Cat*)obj
-
[c eat];
- id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针 ,相当于C语言的 void *
- 因为id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错
-
/// Represents an instance of a class.
-
struct objc_object {
-
Class isa OBJC_ISA_AVAILABILITY;
-
};
-
-
/// A pointer to an instance of a class.
-
typedef struct objc_object *id;
-
id obj = [C at new];
-
[obj eat]; // 不用强制类型转换
-
-
[obj test]; //可以调用私有方法
- 注意:
- 在id的定义中,已经包好了*号。Id指针只能指向OC中的对象
- 为了尽可能的减少编程中出错,Xcode做了一个检查,当使用id 类型的调用本项目中所有类上都没有的方法,编译器会报错
- id类型不能使用.语法, 因为.语法是编译时特性, 而id是运行时特性
17.3.id数据类型与静态类型
- 虽然说id数据类型可以存储任何类型的对象,但是不要养成滥用这种通用类型
- 如没有使用到多态尽量使用静态类型
- 静态类型可以更早的发现错误(在编译阶段而不是运行阶段)
- 静态类型能够提高程序的可读性
- 使用动态类型前*好其真实类型
- 动态类型判断类型
- – (BOOL)isKindOfClass:classObj 判断实例对象是否是这个类或者这个类的子类的实例
-
Person *p = [Person new];
-
Student *stu = [Student new];
-
-
BOOL res = [p isKindOfClass:[Person class]];
-
NSLog(@“res = %i”, res); // YES
-
res = [stu isKindOfClass:[Person class]];
-
NSLog(@“res = %i”, res); // YES
- – (BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
-
Person *p = [Person new];
-
Student *stu = [Student new];
-
-
BOOL res = [p isMemberOfClass:[Person class]];
-
NSLog(@“res = %i”, res); // YES
-
res = [stu isMemberOfClass:[Person class]];
-
NSLog(@“res = %i”, res); // NO
- + (BOOL) isSubclassOfClass:classObj 判断类是否是指定类的子类)
-
BOOL res = [Person isSubclassOfClass:[Student class]];
-
NSLog(@“res = %i”, res); // NO
-
-
res = [Student isSubclassOfClass:[Person class]];
-
NSLog(@“res = %i”, res); // YES
18.new方法实现原理
- java中创建一个对象:Person person = new Person();
在OC中创建一个可用的对象:Person *p=[Person new];
new方法的内部会分别调用两个方法来完成3件事情:- (1)使用alloc方法来分配存储空间(返回分配的对象);
- (2)使用init方法来对对象进行初始化。
- (3)返回对象的首地址
This method is a combination of alloc and init. Like alloc, it initializes the isa instance variable of the new object so it points to the class data structure. It then invokes the init method to complete the initialization process.
- 可以把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.
The isa instance variable of the new instance is initialized to a data structure that describes the class; memory for all other instance variables is set to 0.
- init方法是初始化方法(构造方法),用来对象成员变量进行初始化,默认实现是一个空方法。
An object isn’t ready to be used until it has been initialized. The init method defined in the NSObject class does no initialization; it simply returns self.
- 所以下面两句的作用是等价的
-
Person *p1 = [Person new];
-
Person *p = [[Person alloc] init];// 一般使用该方式创建对象
-
- iOS 程序通常使用[[类名 alloc] init] 的方式创建对象,因为这个可以与其他initWithXX:…的初始化方法统一起来,代码风格保持一致。
19.构造方法
19.1.默认构造方法
- OC的构造方法是以init开头,会java的同学要哭死了,然并卵。
-
– (id)init {
-
self = [super init];
-
if (self) {
-
// Initialize self.
-
}
-
return self;
-
}
-
-
//或者也可以这样
-
//- (id)init {
-
– (instancetype)init {
-
if (self = [super init]) {
-
// Initialize self.
-
}
-
return self;
-
}
- [super init]的作用:
先利用父类的init方法为子类实例的父类部分属性初始化。稍微会点java的都知道什么意思,就不过多解释了。 - self 为什么要赋值为[super init]:
简单来说是为了防止父类的初始化方法release掉了self指向的空间并重新alloc了一块空间。还有[super init]可能alloc失败,这时就不再执行if中的语句。
19.2自定义构造方法
- 自定义构造方法的规范
- (1)一定是对象方法,以减号开头
- (2)返回值一般是instancetype类型
- (3)方法名必须以initWith开头
- 自定义构造方法的声明
-
@interface Person : NSObject
-
-
@property int age;
-
-
@property NSString *name;
-
-
// 当想让对象一创建就拥有一些指定的值,就可以使用自定义构造方法
-
– (instancetype)initWithAge:(int)age;
-
-
– (instancetype)initWithName:(NSString *)name;
-
-
– (instancetype)initWithAge:(int)age andName:(NSString *)name;
-
-
@end
- 自定义构造方法的实现
-
-
-
@implementation Person
-
-
– (instancetype)init
-
{
-
if (self = [super init]) {
-
_age = 10;
-
}
-
return self;
-
}
-
-
– (NSString *)description
-
{
-
return [NSString stringWithFormat:@”age = %i, name = %@”, _age, _name];
-
}
-
-
– (instancetype)initWithAge:(int)age
-
{
-
if (self = [super init]) {
-
_age = age;
-
}
-
return self;
-
}
-
-
– (instancetype)initWithName:(NSString *)name
-
{
-
if (self =[super init]) {
-
_name = name;
-
}
-
return self;
-
}
-
-
– (instancetype)initWithAge:(int)age andName:(NSString *)name
-
{
-
if (self = [super init]) {
-
_age = age;
-
_name = name;
-
}
-
return self;
-
}
19.3.自定义类工厂方法
- 类工厂方法
- 用于快速创建对象的类方法, 我们称之为类工厂方法
- 类工厂方法中主要用于 给对象分配存储空间和初始化这块存储空间
- 规范:
- 一定是类方法 +
- 方法名称以类的名称开头, 首字母小写
- 一定有返回值, 返回值是id/instancetype
- 自定义类工厂方法声明
-
-
-
@interface Person : NSObject
-
-
@property int age;
-
+ (instancetype)person;
-
-
+ (instancetype)personWithAge:(int)age;
-
@end
- 自定义类工厂方法实现
-
-
-
@implementation Person
-
-
+ (instancetype)person
-
{
-
return [[Person alloc] init];
-
}
-
-
+ (instancetype)personWithAge:(int)age
-
{
-
Person *p = [[Person alloc] init];
-
p.age = age;
-
return p;
-
}
-
-
@end
20. instancetype与id
- instancetype == id == 万能指针 == 指向一个对象。
- id在编译的时候不能判断对象的真实类型
- instancetype在编译的时候可以判断对象的真实类型
- instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。id类型可以作为形参,也可以作为方法的返回值。
-
// init此时返回值是id
-
NSString *str = [[Person alloc] init];
-
// Person并没有length方法, 但是id是动态类型, 所以编译时不会报错
-
NSLog(@”length = %i”, str.length);
-
-
—————————-
-
-
// init此时返回值是instancetype
-
// 由于instancetype它会进行类型检查, 所以会报警告
-
NSString *str = [[Person alloc] init];
-
NSLog(@”length = %i”, str.length);
-
-
—————————-
-
instancetype *p = [[person alloc] init];
-
// 错误写法instancetype只能作为返回值
21.类的启动过程
21.1.+load方法
- 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法(只会调用一次)
- load方法加载顺序,在运行时会加载到内存中,会先加载父类的load方法,子类的load方法会在所有的父类加载完之后被加载。*后会是子类的load方法。
- 不管程序运行过程有没有用到这个类,都会调用+load加载
-
@implementation Person
-
-
+ (void)load
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
@implementation Student : Person
-
-
+ (void)load
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
输出结果:
-
+[Person load]
-
+[Student load]
21.2.+initialize
- 在*次使用某个类时(比如创建对象等),只会调用一次+initialize方法
- 一个类只会调用一次+initialize方法,先调用父类的,再调用子类的
- initlalize方法方法是在类或它的子类收到*条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说 +initialize 方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize 方法是永远不会被调用的。那这样设计有什么好处呢?好处是显而易见的,那就是节省系统资源,避免浪费。
-
@implementation Person
-
+ (void)initialize
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
@implementation Student : Person
-
+ (void)initialize
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
int main(int argc, const char * argv[]) {
-
Student *stu = [Student new];
-
return 0;
-
}
-
输出结果:
-
+[Person initialize]
-
+[Student initialize]
- +load 和 +initialize 总结
比较项 | +load | +initialize |
---|---|---|
调用时机 | 被添加到 runtime 时 | 收到*条消息前,可能永远不调用 |
调用顺序 | 父类->子类->分类 | 父类->子类 |
调用次数 | 1次 | 多次 |
是否需要显式调用父类实现 | 否 | 否 |
是否沿用父类的实现 | 否 | 是 |
分类中的实现 | 类和分类都执行 | 覆盖类中的方法,只执行分类的实现 |
22.SEL介绍
22.1.SEL类型
- SEL类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
- 每个类的方法列表都存储在类对象中
- 每个方法都有一个与之对应的SEL类型的对象
- 根据一个SEL对象就可以找到方法的地址,进而调用方法
- SEL类型的定义
- typedef struct objc_selector *SEL;
- 首先把test这个方法名包装成sel类型的数据
- 根据SEL数据到该类的类对象上去找,对应的方法的代码,如果找到了就执行该代码
- 如果没有找到根据类对象上的父类的类对象指针,去父类的类对象中查找,如果找到了,则执行父类的代码
- 如果没有找到,一直像上找,直到基类(NSObject)
- 如果都没有找到就报错。
- 注意:
- 在这个操作过程中有缓存,*次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
- 个人理解在*次查找的时候会速度比较慢,消耗性能,之后在使用的时候应该会先从缓存中进行查找,如果没有,在按照上边的步骤进行查找。找到之后会先缓存,在执行。
-
Dog *dog=[[Dog alloc] init];
-
[dog eat];
22.2.SEL使用
- 定义普通的变量
- 如:SEL sel = @selector(show);
- 作为方法实参与NSObject配合使用
- 检验对象是否实现了某个方法
- – (BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
- + (BOOL)instancesRespondToSelector:(SEL)aSelector;
-
BOOL flag;
-
// [类 respondsToSelector]用于判断是否包含某个类方法
-
flag = [Person respondsToSelector:@selector(objectFun)]; //NO
-
flag = [Person respondsToSelector:@selector(classFun)]; //YES
-
-
Person *obj = [[Person alloc] init];
-
-
// [对象 respondsToSelector]用于判断是否包含某个对象方法
-
flag = [obj respondsToSelector:@selector(objectFun)]; //YES
-
flag = [obj respondsToSelector:@selector(classFun)]; //NO
-
-
// [类名 instancesRespondToSelector]用于判断是否包含某个对象方法
-
// instancesRespondToSelectorr只能写在类名后面, 等价于 [对象 respondsToSelector]
-
flag = [Person instancesRespondToSelector:@selector(objectFun)]; //YES
-
flag = [Person instancesRespondToSelector:@selector(classFun)]; //NO
- 让对象执行某个方法
- – (id)performSelector:(SEL)aSelector;
- – (id)performSelector:(SEL)aSelector withObject:(id)object;
- – (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- respondsToSelector注意点: 如果是通过一个对象来调用该方法那么会判断该对象有没有实现-号开头的方法,如果是通过类来调用该方法, 那么会判断该类有没有实现+号开头的方法
-
Person *p = [Person new];
-
SEL s1 = (objectFun);
-
[p performSelector:s1];
-
-
SEL s2 = (objectFun:);
-
[p performSelector:s2 withObject:@“seltest”];
-
-
SEL s3 = (objectFun:value2:);
-
[p performSelector:s3 withObject:@“gzs” withObject:@“seltest”];
-
-
SEL s4 = (classFun);
-
[Person performSelector:s4];
-
-
SEL s5 = (classFun:);
-
[Person performSelector:s5 withObject:@“seltest”];
-
-
SEL s6 = (classFun:value2:);
-
[Person performSelector:s6 withObject:@“seltest” withObject:@“seltest2”];
- 作为方法形参
-
Person
-
-
- (void)makeObject:(id) obj performSelector:(SEL) selector
-
{
-
[obj performSelector:selector];
-
}
-
-
-
int main(int argc, const char * argv[]) {
-
-
Person *p = [Person new];
-
SEL s1 = ;
-
Dog *d = [Dog new];
-
[p makeObject:d performSelector:s1];
-
-
return 0;
-
}
22.3.OC方法查找顺序
- 1.给实例对象消息的过程(调用对象方法)
- 根据对象的isA指针去该对象的类方法中查找,如果找到了就执行
- 如果没有找到,就去该类的父类类对象中查找
- 如果没有找到就一直往上找,直到根类(NSObject)
- 如果都没有找到就报错(还有三次挽救的机会)
- 2.给类对象发送消息(调用类方法)
- 根据类对象的isA指针去元对象中查找,如果找到了就执行
- 如果没有找到就去父元对象中查找
- 如果如果没有找到就一直往上查找,直到根类(NSOject)
- 如果都没有找到就报错(还有三次挽救的机会)
23.@class使用
23.1.基本概念
- 作用
- 可以简单地引用一个类
- 简单使用
- @class Dog;
- 仅仅是告诉编译器:Dog是一个类;并不会包含Dog这个类的所有内容
- 具体使用
- 在.h文件中使用@class引用一个类
- 在.m文件中使用#import包含这个类的.h文件
- 其它应用场景
- 对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类
- 这种嵌套包含的代码编译会报错
-
#import “B.h”
-
@interface A : NSObject
-
{
-
B *_b;
-
}
-
@end
-
-
#import “A.h”
-
@interface B : NSObject
-
{
-
A *_a;
-
}
-
@end
- 当使用@class在两个类相互声明,就不会出现编译报错
-
@class B;
-
@interface A : NSObject
-
{
-
B *_b;
-
}
-
@end
-
-
@class A;
-
@interface B : NSObject
-
{
-
A *_a;
-
}
-
@end
23.2.@class和#import
- 作用上的区别
- #import会包含引用类的所有信息(内容),包括引用类的变量和方法
- @class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知
- 效率上的区别
- 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦*开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍 , 编译效率非常低
- 相对来讲,使用@class方式就不会出现这种问题了
24.注释和指令
24.1.#pragma mark指令的使用
- 功能:简单来说就是对代码的分组,方便代码查找和导航用的 它们告诉Xcode编译器,要在编辑器窗格顶部的方法和函数弹出菜单中将代码分隔开。一些类(尤其是一些控制器类)可能很长,方法和函数弹出菜单可以便于代码导航。此时加入#pragma 指令(#pragma是一个编译指令)对代码进行逻辑组织很有效果。
- 一个类里我们总会有一些方法的功能与性质是相差不多的,你可能会有把方法们分组的想 法。Xcode已经有了类似的支持,它就是 #pragma mark。
- 分组: #pragma mark 分组(标识)名称
- 分隔线: #pragma mark –
- 分割线加分组: #pragma mark – 分组(标识)名称
24.2.其他指令
- MARK:
- TODO:
- FIXME:
- !!!:
- ???:
基本和#pragma mark指令没神马区别,有图有真相。
25.分类
25.1.什么是分类(Category)
- Category有很多种翻译: 分类 \ 类别 \ 类目 (一般叫分类)
- Category是OC特有的语法, 其他语言没有的语法
- Category的作用
- 可以在不修改原来类的基础上, 为这个类扩充一些方法
- 一个庞大的类可以分模块开发
- 一个庞大的类可以由多个人来编写,更有利于团队合作
25.2.Category的格式
- 在.h文件中声明类别
- 1)新添加的方法必须写在 @interface 与 @end之间
- 2)ClassName 现有类的类名(要为哪个类扩展方法)
- 3)CategoryName 待声明的类别名称
- 4)NewMethod 新添加的方法
-
@interface ClassName (CategoryName)
-
NewMethod; //在类别中添加方法
-
//不允许在类别中添加变量
-
@end
- 在.m文件中实现类别:
- 1)新方法的实现必须写在@ implementation与@end之间
- 2)ClassName 现有类的类名
- 3)CategoryName 待声明的类别名称
- 4)NewMethod 新添加的方法的实现
-
@implementation ClassName(CategoryName)
-
-
NewMethod
-
… …
-
@end
- 使用Xcode创建分类
- command + N创建文件,然后选择本地存储路径。
- 填写分类名称,下一步,然后选择本地存储路径。
25.3.分类的使用注意事项
- 分类只能增加方法, 不能增加成员变量
-
@interface Person (gz)
-
{
-
// 错误写法
-
// int _age;
-
}
-
– (void)eat;
-
@end
- 分类中写property只会生成方法声明
-
@interface Person (gz)
-
// 只会生成getter/setter方法的声明, 不会生成实现和私有成员变量
-
@property (nonatomic, assign) int age;
-
@end
- 分类可以访问原来类中的成员变量
-
@interface Person : NSObject
-
{
-
int _no;
-
}
-
@end
-
-
@implementation Person (gz)
-
– (void)say
-
{
-
NSLog(@”%s”, __func__);
-
// 可以访问原有类中得成员变量
-
NSLog(@”no = %i”, _no);
-
}
-
@end
- 如果分类和原来类出现同名的方法, 优先调用分类中的方法, 原来类中的方法会被忽略
-
@implementation Person
-
-
– (void)sleep
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
@implementation Person (gz)
-
– (void)sleep
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
int main(int argc, const char * argv[]) {
-
Person *p = [[Person alloc] init];
-
[p sleep];
-
return 0;
-
}
-
-
输出结果:
-
-[Person(gz) sleep]
25.4.分类的编译的顺序
- 多个分类中有同名方法,则执行*后编译的文件方法
-
@implementation Person
-
-
– (void)sleep
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
@implementation Person (gz)
-
– (void)sleep
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
@implementation Person (zz)
-
– (void)sleep
-
{
-
NSLog(@”%s”, __func__);
-
}
-
@end
-
-
int main(int argc, const char * argv[]) {
-
Person *p = [[Person alloc] init];
-
[p sleep];
-
return 0;
-
}
-
-
输出结果:
-
-[Person(zz) sleep]
- 方法调用的优先级(从高到低)
- 分类(*后参与编译的分类优先)
- 原来类
- 父类
25.5类扩展
- 延展类别又称为扩展(Extendsion),Extension是Category的一个特例
- 对比分类,类延展只少了个分类的名字,所有又称之为“匿名分类”
- 可以为某个类扩充一些私有的成员变量和方法
- 写在.m文件中
- 英文名是Class Extension
- 类扩展书写格式
-
@interface 类名 ()
-
@end
- 例子
-
-
-
@interface Person()
-
-
// 没在.h文件中声明,属于私有属性
-
@property (nonatomic, strong) NSArray *array;
-
-
// 没在.h文件中声明,属于私有方法
-
– (void)say;
-
-
-
@end
-
-
@implementation Person
-
-
– (void)say{
-
// TODO
-
}
-
-
@end
26. Block
26.1.Block介绍
- Block是iOS中一种比较特殊的数据类型。在java中没有可以与之相对于的语法。如果你有c语言的基础,明白指针的原理,那就感觉容易多了。个人理解Block就是一个指向函数的指针。该指针指向了一块内存,该内存中存储了一段代码,当你需要执行这一段代码块的时候就根据这个指针找到对应的内存地址,然后就可以执行对应的代码。当然这只是简单说明了Block的作用,在实际开发中还是有很多地方需要好好的研究的。比如,block内部引用对象,如何防止循环引用,如何修改block外边的变量,block中内存如何管理等等,想想就崩溃,这是后话。废话不多说,直接看语法。
- Block是苹果官方特别推荐使用的数据类型, 应用场景比较广泛
- 动画
- 多线程
- 集合遍历
- 网络请求回调
26.2.block的格式
- Block的定义格式
-
返回值类型 (^block变量名)(形参列表) = ^(形参列表) {
-
-
};
- block*简单形式
-
void (^block名)() = ^{代码块;}
-
-
例如:
-
void (^myBlock)() = ^{ NSLog(@”block”); };
- block带有参数的block的定义和使用
-
void (^block名称)(参数列表)
-
= ^ (参数列表) { // 代码实现; }
-
-
例如:
-
void (^myBlock)(int) = ^(int num){ NSLog(@”num = %i”, num); };
- 带有参数和返回值的block
-
返回类型 (^block名称)(参数列表)
-
= ^ (参数列表) { // 代码实现; }
-
-
例如:
-
int (^myBlock)(int, int) = ^(int num1, int num2){ return num1 + num2; };
- 调用Block保存的代码
-
block变量名(实参);
-
例如:
-
// 调用了block的*简单形式
-
myBlock();
-
-
// 调用了带有参数的block
-
myBlock(10);
-
-
// 调用了带有参数和返回值的block
-
myBlock(10, 20);