RE0_OC_3

50 阅读11分钟

(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

img

转换:(最好不用,不可逆)

选中要转换的文件,然后edit,转换为ARC

img

(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类型

img

(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];

——为什么实例化的对象要求遵守协议?

——因为我要调对象的这个方法,只有遵守这个协议才有这个方法!