(21)使用.访问对象的属性
OC对象要为对应的对象取值或赋值,都必须要通过getter和setter,赋值时使用[s1 setXXX],读值时使用[s1 xxx]
但在类中已经使用@property声明了该属性或实现了getter和setter方法时,可直接通过.来赋值;
@interface Person : NSObject
@property (nonatomic,strong) NSString *name;
@end
// Student类继承于Person类
Student *s1 = [Student new];
s1.name = @"hh"; //赋值
NSLog(@"学生姓名:%@",s1.name); //取值
本质上,.符号是代码自动调用getter和setter实现的;
因此,如果自己实现了getter和setter方法,那么尽量避免在getter和setter方法中使用.语法,有可能会引起无限递归导致程序崩溃;
(22)NSObject和id的异同
相同点:万能指针,都可以指向任意对象;
NSObject *obj1 = [Person new];
NSObject *obj2 = [Student new];
//id是typedef类型,定义的时候已经加了*了,声明指针时就不需要再加*了
id id1 = [Person new];
id id2 = [Student new];
不同点:
通过NSObject创建的万能指针不能直接调用类的对象方法,需要进行强制转换,否则编译时会报错;
通过id创建的万能指针可以直接调用类的对象方法,不会报错;但不能使用点语法;
因此,如果要声明一个万能指针,建议使用id,不要用NSObject;
(23)respondsToSelector
在调用之前,判断对象中是否有这个方法可以执行;(判断是否有对象方法)
- (BOOL)respondsToSelector:(SEL)aSelector;
Student *s1 = [Student person];
BOOL b1 = [s1 respondsToSelector:@selector(lenth)];
if (b1 == YES) {
[s1 setName:@"王铁锤" andAge:30];
}
else{
NSLog(@"没有方法");
}
(24)isKindOfClass
判断指定的对象是否为 指定类的对象或者子类对象.
- (BOOL)isKindOfClass:(Class)aClass;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [s1 isKindOfClass:[Person class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Person类");
}
(25)isMemberOfClass
判断对象是否为指定类的对象 不包括子类.
- (BOOL)isMemberOfClass:(Class)aClass;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [s1 isMemberOfClass:[Student class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
(26)isSubclassOfClass
判断类是否为另外1个类的子类.
+ (BOOL)isSubclassOfClass:(Class)aClass;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [Student isSubclassOfClass:[Person class]];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
(27)instancesRespondToSelector
判断类中是否有指定的类方法.
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
Student *s1 = [Student person];
[s1 setName:@"王铁锤" andAge:30];
BOOL b1 = [Student instancesRespondToSelector:@selector(setName:andAge:)];
if (b1 == YES) {
NSLog(@"name:%@ age:%d",s1.name,s1.age);
}
else{
NSLog(@"不是Student类");
}
(28)init方法
一些需要知道的:
Person *p1 = [Person new];
//完全等价于
Person *p2 = [[Person alloc] init];
alloc:为类创建一个对象,并返回
init:为这个对象初始化属性,基本数据类型初始化为0,C数据类型初始化为NULL,OC数据类型初始化为Nil;
那么如果想要创建对象时,想要对象的属性值不是默认值,那么就需要重写init方法;
重写init的规范:
1)、必须要先调用父类的init方法.然后将方法的返回值赋值给self
因为父类的init方法,会初始化父类的属性。所以必须保证当前对象中的父类属性也同时被初始化,然后将初始化后的对象返回给当前对象,也就是self;
- (instancetype)init
{
if(self = [super init])
{
//初始化当前类的属性的代码;
}
retrun self;
}
2)、调用init方法初始化对象有可能会失败,如果初始化失败.返回的就是nil
3)、判断父类是否初始化成功. 判断self的值是否为nil 如果不为nil说明初始化成功.
4)、如果初始化成功就初始化当前对象的属性.
5)、最后返回self的值.
(29)initWithXxx
上面的init方法重写属性后,只有每次初始化的默认属性都是一样的,如果要想初始化的值由调用人指定的话,那么可以自定义一个构造方法,用来接收调用者的传参;但自定义构造的方法必须是initWith开头
(30)@selector
@selector是Objective-C语言中用于动态绑定方法的特殊语法,通过方法名称和参数类型生成唯一的选择器(SEL类型),实现运行时方法调用
调用格式:
@selector(methodName)
其中methodName为方法名称。生成的SEL类型相当于方法的“编号”,用于快速定位和调用;
作用:
@selector(methodName)就是找到方法名为methodName的方法,然后返回值为SEL数据类型;如果有一个方法的返回类型是SEL,那么就是需要接受@selector返回的参数;
(31)@property的参数
@property (参数1,参数2,参数3,...)数据类型 参数名;
1、与多线程相关的两个参数
atomic:默认值,生成的setter方法会被加上一个线程安全锁,特点:安全但效率低
nonatomic:使用nonatomic,生成的setter方法不加线程安全锁,特点,不安全,但效率高
建议: 要效率. 选择使用nonatomic 在没有讲解多线程的知识以前 统统使用nonatomic
2、与生成的setter方法的实现相关的参数
assign:默认值,生成的setter方法的实现就是直接赋值;
retain:生成的setter方法的实现就是标准的MRC内存管理代码;
3、与生成只读、读写相关的参数
readwrite:默认值,代表同时生成getter setter
readonly: 只会生成getter,不会生成setter
4、属性的强弱类型(ARC模式下)
strong: 只能使用在ARC机制下. 当属性的类型是OC对象类型的时候,绝大多数情况下使用strong;
weak:只能使用在ARC机制下. 当属性的类型时OC对象时且出现循环引用时,一端用strong,一端用weak;
(32)@class
定义
当两个类相互包含的时候,你引入了我,我引入了你的时候,就会出现循环饮用的问题,会造成无限递归,从而导致无法编译通过;
如:Person.h中引入了Book.h,Book.h引入了Person.h
那么如何解决?
其中一方引入时不要使用#import,而是在.h头文件中使用@class来标注这个类,然后在.m文件中再#import对方的头文件
区别
#import和@class的区别:
#import是将指定的文件的内容拷贝到写指令的地方。
@class 并不会拷贝任何内容,只是告诉编译器这是1个类,这样编译器在编译的时候才可以知道这是1个类。
(33)retain的相互引用
当一个类互相作为另一个类的属性时,如果两个都是retain类型的属性, 那么就会出现内存泄露,程序无法正常运行
解决方法:一个类的属性使用retain,另一个类的属性使用assign即可;而且使用addign的那一个类不需要再dealloc中release;
(34)MRC与ARC的兼容与转换
兼容:
在ARC的文件中如果要引入MRC的文件,直接编译会报错;
需要在Build Phase阶段,对MRC文件,添加命令:-fno-objc-arc
转换:(最好不用,不可逆)
选中要转换的文件,然后edit,转换为ARC
(35)block(重点)
定义
block是1个数据类型.
—— 那么block类型的变量中可以存储什么样的数据呢?
—— block类型的变量中专门存储1段代码. 这段代码可以有参数 可以有返回值.
在声明block变量的时候,必须要指定这个block变量存储的代码段是否有参数,是否有返回值。
而且一旦指定以后,这个block变量中就只能存储这样的代码了。
语法
返回值类型 (^block变量的名称)(参数列表);
void (^myBlock)();:声明了一个block变量,然后这个变量中只能存储没有返回值,没有参数的代码段;
eg:
int (^myBlock1)()
int (^myBlock2)(int num1,int num2)
初始化Block
写一个满足block变量的代码段,然后存储到block变量中;
代码段的语法格式
^返回值类型(参数列表){
代码段
}
如何存储到block变量中
block变量名 = ^返回值类型(参数列表){
代码段
}
eg:
(1)一个无参数无返回值的代码段
^void(){
NSLog(@"我爱你");
NSLog(@"我恨你");
};
这个时候,我们就可以将这段代码使用赋值符号存储到 无返回值无参数要求的block变量中.
void (^myBlock)();
myBlock = ^void(){
NSLog(@"我爱你");
NSLog(@"我恨你");
};
当然也可以在声明block变量的同时使用符合要求的代码段初始化.
void (^myBlock)() = ^void(){
NSLog(@"我爱你");
NSLog(@"我恨你");
};
如何执行
如何执行block存储的代码段?
语法格式:
block变量名(传参);
简写
(1)当代码段没有返回值,那么代码段的void可以省略
void (^myBlock)() = ^(){
NSLog(@"我爱你");
NSLog(@"我恨你");
};
(2)当代码段没有参数,那么代码段的小括号可以省略
void (^myBlock)() = ^void{
NSLog(@"我爱你");
NSLog(@"我恨你");
};
(3)当代码段即没有返回值,又没有参数,则代码段的返回值和小括号都可以省略
(3)当代码段即没有返回值又没有参数时,都可以省略
void (^myBlock)() = ^{
NSLog(@"我爱你");
NSLog(@"我恨你");
};
(4)在声明block变量时,如果有指定参数,声明时可以只写参数的类型,不写参数的名称
int (^myBlock3)(int,int) = ^int(int num1,int num2){
int num3= num1 + num2;
return num3;
};
(5)无论代码段是否有返回值,在写代码的时候,可以不写返回值类型。
返回的数据是什么类型 它就会认为这个代码段是什么类型的.
int (^myBlock3)() = ^(int num1,int num2){
return num1+num2
}
(5)简化block变量的定义
如果block的变量定义很长,那么可以使用typedef来将一个长类型定义为短类型
typedef 返回值类型 (^新类型)(参数列表);
typedef void (^NewType)();
代表重新定义了1个类型叫做NewType 是1个block类型 无参数无返回值的block类型
(36)block访问内外部变量
block代码块中:
可以访问全局变量和内外部的局部变量,可以修改全局变量和代码块内部的局部变量
但不可以修改代码块外部的局部变量
#import <Foundation/Foundation.h>
typedef void (^newType) ();
int num1 = 100;
int main(int argc, const char * argv[]) {
int num2 = 200;
newType newBlock = ^void(){
int num3 = 300;
num1++; //允许
num2++; //报错
num3++; //允许
// 可访问全局变量和内外部局部变量
NSLog(@"当前num1值,%d",num1);
NSLog(@"当前num2值,%d",num2);
NSLog(@"当前num3值,%d",num3);
};
newBlock();
return 0;
}
如果想要修改代码块外部的局部变量,则需要为要修改的局部变量前加__block修饰符
#import <Foundation/Foundation.h>
typedef void (^newType) ();
int num1 = 100;
int main(int argc, const char * argv[]) {
__block int num2 = 200; //加——block修饰符
newType newBlock = ^void(){
int num3 = 300;
num1++; //允许
num2++; //允许
num3++; //允许
// 可访问全局变量和内外部局部变量
NSLog(@"当前num1值,%d",num1);
NSLog(@"当前num2值,%d",num2);
NSLog(@"当前num3值,%d",num3);
};
newBlock();
return 0;
}
(37)block作为函数的参数
1、如何传参
typedef void (^newType) ();
void testFunc(newType nb1){
nb1();
};
2、如何调用
如果要调用一个传参为block的函数,那么就要传入一个和block定义相同类型的代码段
#import <Foundation/Foundation.h>
typedef void (^newType) ();
void testFunc(newType nb1){
nb1();
};
int main(int argc, const char * argv[]) {
newType params = ^void(){
NSLog(@"我是传参");
};
testFunc(params);
return 0;
}
(38)protocol协议
作用:
1). 专门用来声明一大堆方法。(不能声明属性,也不能实现方法,只能用来写方法的声明)。
2). 只要某个类遵守了这个协议。就相当于拥有这个协议中的所有的方法声明。而不用自己去定义。
语法:
@interface 类名:父类名 <协议名1,协议名2>
类是单继承,但协议可以有多个
类对协议的遵守
类遵守某个协议后,可以不实现协议中声明的方法,编译器会告警但不会报错
(39)@require和@optical
protocol声明的方法可以用关键词来修饰;
@require:(默认修饰符)如果使用@require修饰声明的方法,那么遵守这个协议的类必须要实现这个方法,否则会警告;
@optical:如果使用@optical修饰声明的方法,则可以选择不实现这个方法,也不会告警;
事实上,无论用哪个关键词修饰,都可以选择不实现,只是为了告知程序员,哪些是需要实现的
(40)protocol的类型限制
要求某个指针保存的是遵守指定协议的对象.
语法:
声明一个指针对象,这个指针对象遵守单个协议
NSObject<StudyProtocol> *obj = [Student new];
id<StudyProtocol> id1 = [Student new];
声明一个指针对象,这个指针对象遵守多个协议
NSObject<StudyProtocol,SBProtocol> *obj1 = [Student new];
id<StudyProtocol,SBProtocol> obj1 = [Student new];
——为什么实例化的对象要求遵守协议?
——因为我要调对象的这个方法,只有遵守这个协议才有这个方法!