iOS学习之 Objective - C 基础知识点

Objective-C

1. import的用法

  • 拷贝文件内容
    可以自动防止文件的内容被重复拷贝(#define宏定义)
  • Foundation 框架头文件的路径
    Xcode.app 显示包内容
    Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/Foundation.framework
  • 命令行指令
  • 编写 Oc 源文件: .m .c
  • 编译: cc -c xxx.m xxx.c
  • 链接: cc xxx.o xxx.o -framework Foundation(用到的时候才加)
  • 运行: ./a.out
  • 主头文件
    主头文件:最主要的头文件,名字一般跟框架名称一样,包含了框架中的所有其他头文件
  • Foundation框架的主头文件名称就是Foundation.h
    只需要包含Foundation框架主头文件,就可以使用整个框架的东西

2. 对象方法和类方法

  • 对象方法: – 开头
    • 只能由对象调用
    • 对象方法中能访问当前对象的成员变量(实例变量)
  • 类方法: + 开头
    • 只能由类名来调用
    • 类方法中不能访问成员变量
    • 类方法的优点和使用场合:
    1. 不依赖于对象,执行效率高
    2. 能用类方法,尽量用类方法
    3. 场合:当方法内部不需要使用到成员变量时,就用类方法
    4. 可以允许类方法和对象方法同名

3. 成员变量和局部变量

  • 成员变量:

    • 写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)成员变量只能通过对象来访问
    • 注意: 成员变量不能离开类,,离开类之后就不是成员变量,成员变量不能在定义的同时进行初始化
    • 存储: 堆(当前对象对应的堆的存储空间中)
    • 存储在堆中的数据, 不会被自动释放, 只能程序员手动释放
  • 局部变量:

  • 写在函数或者代码块中的变量, 我们称之为局部变量

    • 作用域: 从定义的那一行开始, 一直到遇到大括号或者return
    • 局部变量可以先定义再初始化, 也可以定义的同时初始化
    • 存储 : 栈
    • 存储在栈中的数据有一个特点, 系统会自动给我们释放
  • 全局变量

    • 写在函数和大括号外部的变量, 我们称之为全局变量
    • 作用域: 从定义的那一行开始, 一直到文件末尾
    • 局部变量可以先定义在初始化, 也可以定义的同时初始化
    • 存储: 静态区
    • 程序一启动就会分配存储空间, 直到程序结束才会释放

4. 方法和函数

  • 方法
  • 函数属于整个文件,, 方法属于某一个类,方法如果离开类就不行
  • 函数可以直接调用, 方法必须用对象或者类来调用
  • 函数
  • 能写在文件中的任意位置(@interface和@end之间除外),函数归文件所有
  • 函数调用不依赖于对象
  • 函数内部不能直接通过成员变量名访问某个对象的成员变量

5. setter 和 getter 用法简介

  • setter
  • 作用: 给成员变量赋值
  • 格式:
    1. 必须是对象方法
    2. 一定没有返回值
    3. 方法名称一定以set开头, set后面跟上成员变量的名称, 并去掉下划线, 然后将首字母大写
    4. 一定有参数, 并且参数类型和成员变量的类型一致, 参数名称就是成员变量的名称去掉下划线
  • getter
  • 作用: 返回成员变量的值
  • 格式:
    1. 必须是对象方法
    2. 一定有返回值, 返回值类型和成员变量的类型一致
    3. 方法名称就是成员变量的名称去掉下划线
    4. 一定没有参数
  • readLoad 和 readWrite
  • 一个属性可以只有getter方法, 没有setter方法, 这种属性我们称之为只读属性
  • 一个属性也可以只有setter方法, 没有getter方法, 这种属性我们称之为只写属性
  • 如果既有setter方法又有getter方法, 那么这种属性我们称之为可读可写的属性
  • 一个属性也可以没有getter和setter, 这种属性我们称之为私有属性

6. self 的基本使用

  • 用法
  • 那个调用了当前方法,self就代表谁
  • self出现在对象方法中, self就代表对象
  • self出现在类方法中, self就代表类
  • 在对象方法利用 “self->成员变量名” 访问当前对象内部的成员变量(实例变量)
  • [self 方法名] 可以调用其他对象方法\类方法
  • self会自动区分类方法和对象方法, 如果在类方法中使用self调用对象方法, 那么会直接报错
  • 不能在对象方法或者类方法中利用self调用当前self所在的方法
  • 动态绑定:
    动态类型能使程序直到执行时才确定对象的真实类型
    动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法
  • 使用self调用本方法,导致死循环调用
  • #pragma mark
  • 将代码分隔开,方便我们进行查找。

7. super 基本使用

  • 编译器指令符号.
  • 利用super给父类的方法发送一个消息, 那么系统就会自动调用父类的方法
  • 如果以后想在子类中调用父类的方法可以使用super
  • 如果想在给父类方法进行扩展的同时保留父类的方法, 那么可以使用super调用父类同名的方法
  • super 在什么方法中就调用父类的什么方法

8. 面向对象基本思想

封装

  • 原理:屏蔽内部实现的细节,仅仅对外提供共有的方法/接口
  • 好处:保证数据的安全性
  • 规范:一般情况下不会对外直接暴露成员变量, 都会提供一些共有的方法进行赋值成员变量都需要封装起来

继承

  • 父类必须声明在子类的前面
  • 不允许子类和父类拥有相同名称的成员变量, 因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义。
  • 调用某个对象的方法时, 优先去当前类中找, 如果么有, 去父类中找
  • 基类的私有属性能被继承, 不能在子类中访问。
  • OC中的继承是单继承:也就是说一个类只能一个父类, 不能继承多个父类
  • 缺点:耦合性太强

多态

  • 事物的多种形态
  • 没有继承就没有多态
  • 代码的体现: 父类类型的指针指向子类对象
  • 好处: 如果函数\方法参数中使用的是父类类型, 可以传入父类, 子类对象
  • 局限性: 父类类型的变量 不能 直接调用子类特有的方法,必须强转为子类类型变量后, 才能直接调用子类特有的方法
  • 动态绑定:
    • 动态类型能使程序直到执行时才确定对象的真实类型
    • 动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法
    假设 子类 Dog 有一个特有的方法bark
    [dog bark];
    Animal *an = [Dog new];
    [(Dog*)an bark]; //把父类的指针,强制类型转换    
    
    

9. 成员变量的作用域

  • @public 在任何地方都能直接访问对象的成员变量
  • @private 只能在当前类的对象方法中直接访问(子类可以通过seter geter方法访问父类的私有的成员变量)
  • @protected 能在当前类和子类的对象方法中直接访问 (默认是 protected)
  • @package 只要处在同一个框架中, 就能直接访问对象的成员变量(不常用)
    点语法使用注意
  • 点语法的本质还是方法调用
  • p.age = 10; // [p setAge:10]
  • 引发死循环
    self.age = age; // [self setAge:age]
  • 私有成员变量
  • 写在@implementation 中的成员变量,默认就是私有成员变量,并且和利用@private 修饰的不太一样,@implementation 中定义的成员变量在其他类中无法查看,也无法访问
    在@implementation 中定义的私有变量只能在本类中查看
  • 私有方法:只有实现没有声明,OC 中美有真正的私有方法,因为 OC 是消息机制。私有方法外面不能访问,只能通过包装成 sel 就可以访问

10. @property 用法

  • Property 编译器指令

  • 生成setter 和 getter 方法声明(未加强版)

    -(void)setAge:(int)age;
    -(int)age;
     @property int age;
    
  • @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;
    
    
  • Property 增强

  • 只要利用一个@property就可以同时生成setter/getter方法的声明和实现传入的属性赋值给_开头的成员变量

  • @property有一个弊端: 它只会生成最简单的getter/setter方法的声明和实现, 并不会对传入的数据进行过滤

  • 如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property

  • 注意: 如果没有会自动生成一个_开头的成员变量,自动生成的成员变量是私有变量, 声明在.m中,在其它文件中无法查看,但当可以在本类中查看

  • 有就不生成,没有就生成

    • 如果重写了setter方法, 那么property就只会生成getter方法
    • 如果重写了getter方法, 那么property就只会生成setter方法
    • 如果同时重写了getter/setter方法, 那么property就不会自动帮我们生成私有的成员变量
  • @property(属性修饰符) 数据类型 变量名称;
    readwrite:代表生成 getter 和 setter 方法,默认就是
    readonly:代表只生成 getter 方法,(只读)

  • 修改 getter 方法名(常用)
    程序员之间有一个约定, 一般情况下获取BOOL类型的属性的值, 我们都会将获取的方法名称改为isXXX

11. new alloc init 的基本用法及区别

  • alloc
  • 开辟存储空间
  • 将所有成员变量设为0
  • 返回当前的对象地址
  • init
  • 初始化成员变量, 但是默认情况下init的实现是什么都没有做 2.返回初始化后的实例对象地址
  • alloc和 init 返回的地址是一样的

12. id和 instancetype

  • 静态类型和动态类型
  • 静态类型:将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,这个变量总是存储特定类的对象。
    Person *p = [Person alloc] init]]
  • 动态类型:这一特性是程序直到执行时才确定对象所属的类
    id p = [[Person alloc] init];
  • Id
  • id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针
  • id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错
  • 优点
    • 通过静态数据类型定义变量, 不能调用子类特有的方法
    • 通过动态数据类型定义变量, 可以调用子类特有的方法
    • 通过动态数据类型定义的变量, 可以调用私有方法
  • 弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
  • 应用场景
  • 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
  • 为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个变量的方法之前会进行一次判断, 判断当前变量是否能够调用这个方法
id obj = [Student new];
[obj isKindOfClass:[Student class]]
//isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
  • instancetype
  • instancetype == id == 万能指针 == 指向一个对象
  • id在编译的时候不能判断对象的真实类型
  • instancetype在编译的时候可以判断对象的真实类型
  • id和instancetype除了一个在编译时不知道真实类型, 一个在编译时知道真实类型以外, 还有一个区别
  • id可以用来定义变量, 可以作为返回值, 可以作为形参
  • instancetype只能用于作为返回值
  • 注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id

13. 构造方法(- 开头的对象方法)

  • 用来初始化对象的方法.

  • 重写构造方法的注意:

  • 先调用父类的构造方法.

  • 再进行子类内部的成员变量的初始化

  • 返回当前对象的地址

     -(instancetype)init
     {
        // 注意: 不要把 = 号写为 ==
        // 一定要将[super init]的返回值赋值给self
        if (self = [super init]) {
        // 初始化子类
        _age = 6;
        }
            return self;
    }    
    
    
  • 自定义构造方法

  • 自己做自己的事情

  • 父类的属性交给父类的方法来处理,子类的方法处理子类自己独有的属性

  • 自定义构造方法必须以intiWith开头,并且’W’必须大写

  • 类工厂方法:

  • 用于快速创建对象的类方法, 我们称之为类工厂方法

  • 类工厂方法中主要用于 给对象分配存储空间和初始化这块存储空间

  • 规范:

    • 一定是类方法 +
    • 方法名称以类的名称开头, 首字母小写
    • 一定有返回值, 返回值是id/instancetype
    • 注意: 以后但凡自定义类工厂方法, 在类工厂方法中创建对象一定不要使用类名来创建,一定要使用self来创建
      return [[self alloc] init];

14. Category – 分类

  • 在不改变原来类模型的前提下, 给类扩充一些方法. 有2种方式 : 继承 分类
  • 好处: 一个庞大的类可以分模块开发, 一个庞大的类可以由多个人来编写, 便于团队合作.
  • 使用注意:
  • 分类中写property, 只会生成getter/setter方法的声明, 不会生成实现和私有成员变量
  • Category 可以访问原始类的成员变量, 但不能添加变量, 只能添加方法. 如果想 添加变量, 可以考虑通过继承创 建子类
  • Category 可以实现原始类的方法, 不推荐这么做, 因为它是替换掉原始类的方 法, 这么做以后就不能访问原来的 方法.
  • 多个Category 中如果实现了相同的方法, 只有最后一个参与编译的才会有效.
  • 方法调用的优先级 : 分类(最后参与编译的分类优先) —>原来类—>父类
  • 类扩展(Extendsion)
  • 某个类扩充一些私有的成员变量和方法
  • 写在.m文件中
  • 英文名是Class Extension
  • 格式(俗称匿名分类)
        @interface 类名 ()
        @end
        
    

15. 类的本质(typedef struct objc_class * Class)

  • 类也是一个对象, 是 class 类型的对象, 简称 “类对象”, 类名就代表着类对象, 每个类只有一个类对象
  • + load
  • + load : 在程序启动的时候会加载所有的类和分类, 并调用所有类和分类的 + load 方法,并且只会调用一次。
  • 加载顺序 父类 → 子类→ 分类。不管程序运行过程有没有用到这个类, 都会调用 + load 方法。
  • + initialize
  • + initialize : 在第一次使用某个类时(比如创建对象等), 且只会调用一次 + initialize 方法.
  • 主要用于对某一个类一次性初始化
  • 一个类只会调用一次 + initialize方法, 先调用父类的, 再调用子类的
  • 获取类对象的2种方式(获取内存中的类对象)
```
    Class c = [Person class]     // 类方法
    Person *p = [Person new];
    Class c1 = [p class];  // 对象方法

```
  • 类在内存的表现
    • 实例对象 → 类对象(对象方法)→ 元类对象(类方法)→ 根元类 (isa 指向自己)
  • 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。
  • 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
  • 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
  • 由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
  • 如下图 :

    《iOS学习之 Objective - C 基础知识点》 Snip20150623_6.png

16. NSLog

  • 使用 NSLog 和 %@输出某个类对象时, 会调用类对象 + description 方法, 并拿到返回值(NSString *)进行输出
  • – description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
    • description方法是基类NSObject 所带的方法. 使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法 的默认实现
      -(NSString *) description
      {
              return [NSString stringWithFormat:@"age = %d", _age]
      }
      
      
  • + descrption 方法
  • 当使用NSLog输出该类的类对象的时候调用*/(不常用
  • 注意: 死循环. 如果在 – description方法中使用 NSLog %@ 输出self对象会引发死循环

17. SEL 基本使用

  • 代表方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系

  • 每个方法都有一个与之对应的 SEL类型的对象

    • SEL 其实是对方法的一种包装, 将方法包装成一个 SEL 类型的数据, 去找对应的方法地址, 进而进行调用
    • 注意:在这个操作过程中又缓存,第一次找的时候一个一个的找,非常耗性能,之后再用到的时候就直接使用
  • 对象是否实现了某个方法

    • – (BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
    • + (BOOL)instancesRespondToSelector:(SEL)aSelector; (类对象)
  • 让对象执行某个方法

  • – (id)performSelector:(SEL)aSelector;

  • SEL 类型的定义 typedef struct objc_selector *SEL

    //SEL 对象的创建
     SEL s = @selector(test);
     SEL s2 = NSSelectorFromString(@"test”);
    // 将SEL对象转为NSString对象
    NSString *str = NSStringFromSelector(@selector(test));
    Person *p = [Person new];
    // 每个类都有以个_cmd 代表当前方法
    // 调用对象p的test方法
    [p  performSelector : @selector (test)];
    

18. 内存管理

Automatic Reference Couting

  • 什么是自动引用计数器
    • 每个OC对象都有自己的引用计数器,它是一个整数,从字面上, 可以理解为”对象被引用的次数”
    • 也可以理解为: 它表示有多少人正在用这个对象
    • 占4个字节

Manul Refrence Counting

  • 什么是手动引用计数?

    • 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
  • 方法的基本使用

    • retain : 计数器 +1 , 会返回对象本身
    • release : 计数器 -1 , 没有返回值(release并不代表销毁对象, 仅仅是计数器-1
    • retainCount : 获取当前的计数器
  • 概念

    • 僵尸对象: 所占用内存已经被回收的对象, 僵尸对象不能再使用
    • 野指针: 指向僵尸对象(不可用内存)的指针, 给野指针发送消息会报错(EXC_BAD_ACCES)
    • 空指针: 没有指向任何东西的指针(储存的东西是nil NULL 0), 给空指针发送消息不会报错
  • 内存管理代码规范
  • 只要调用了alloc, 必须有relese(autorelease), 如果对象不是通过alloc产生的, 就不需要release
  • set方法的代码规范
    • 基本数据类型: 直接复制
    -(void)setAge:(int)age
        {
        _age = age;
        }        
    ```
     -  OC对象类型
    ```
    -(void)setCar:(Car *)car
    {
    // 先判断是不是新传进来对象
        if( car != _car)
        {
    // 对旧对象做一次release
        [_car release]
    // 对新对象做一次retain
        _car = [car retain]
        }
    }
    ```
    
  • dealloc方法的代码规范
    • 对self(当前)所拥有的其他对象做一次release
    • 当一个对象要被回收的时候, 就会调用
    • 一定要调用[super dealloc], 这句调用放在最后面

@Property 参数

  • set 方法内存管理相关的参数
    • retain : release 旧值 , retain 新值 (适用于OC对象类型)
    • assign : 直接赋值(默认, 适用于非OC对象类型)
    • copy : release 旧值, copy 新值
  • 是否要生成set方法
    • readwrite : 同时生成setter 和 getter的声明, 实现(默认)
    • readonly : 只会生成getter的声明, 实现
  • 多线程管理
    • nonatomic : 性能高 (一般就用这个)
    • atomic : 性能低(默认)
  • setter 和 getter方法的名称
    • setter : 决定了set方法的名称, 一定要有个冒号 :
    • getter : 决定了get方法的名称(一般用在BOOL类型)

@Class(循环引用)

  • 仅仅告诉编译器,某个名称是一个类

  • 开发中引用一个类的规范

    • 在.h 文件中用@class 来声明类
    • 在.m 文件中用#import 来包含类的所有东西
  • 和#import 的区别(面试)

    • import会包含引用类的所有信息(内容),包括引用类的变量和方法
    • @class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知
  • 总结:

    • 如果都在.h中import, 假如A拷贝了B, B拷贝了C , 如果C被修改了, 那么B和A都需要重新拷贝. 因为C修改了那么B就会重新拷贝, 而B重新拷贝之后相当于B也被修改了, 那么A也需要重新拷贝. 也就是说如果都在.h中拷贝, 只要有间接关系都会重新拷贝
    • 如果在.h中用@class, 在.m中用import, 那么如果一个文件发生了变化, 只有和这个文件有直接关系的那个文件才会重新拷贝
    • 所以在.h中用@class可以提升编译效率
  • 两端循环引用(面试)

  • retain
    * 比如A对象retain了B对象,B对象retain了A对象,这样会导致A对象和B对象永远无法释放。
    * 当两端互相引用时,应该一端用retain、一端用assign。

  • import
    * 如果两个类相互(#import<>)拷贝, 例如A拷贝B, B拷贝A, 这样会报错
    – 如何解决: 在.h中用@class, 在.m中用import
    – 因为如果.h中都用import, 那么A拷贝B, B又拷贝A, 会形成死循环
    – 如果在.h中用@class, 那么不会做任何拷贝操作, 而在.m中用import只会拷贝对应的文件, 并不会形成死循环

@ autorelease基本用法

  • 会将对象放到一个自动释放池中,并且会返回对象本身
  • 当自动释放池被销毁时, 会对池子里面的所有对象做一次release 操作
  • 调用完@autorelease 方法后,对象计数器不变
  • @autorelease 的好处
    • 不用关心对象释放的时间
    • 不用关心什么时候调用 release
  • @autorelease 使用注意
    • 占用内存较大的对象不要随便用 autorelease
    • 占用内存较小的对象使用 autorelease,没有太大影响(影响:不能控制对象的释放时间)
  • 错误写法
    • alloc 之后调用了 autorelease ,又调用 release
      Person *p = [[[Person alloc] init] autorelease];
      [p release];
    • 连续调用autorelease(野指针错误,每个autorelease 释放时都会调用 release)
      Person *p = [[[[Person alloc] init] auturelease] autorelease]
  • 系统自带方法里面没有alloc、new、copy,说明返回的对象是autorelease的
    • 开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
    • 创建对象时不要直接用类名,一般用 self
  + (id)person
   {
       return [[[self alloc] init] autorelease];
   }
  • 自动释放池

  • 在 IOS 程序运行中,会创建无数个池子。这些池子都是以“栈”结构存在(先进后出,“杯子”)

  • 当一个对象调用 autorelease 方法时,会将这个对象放到栈顶的释放池 (”栈顶“ 相当于杯子底)

  • 集合对象的内存管理

    • 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1
    • 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1
    • 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1

ARC

  • ARC 的判断准则:主要没有强指针指向对象,就会释放对象

  • 指针的2种类型

  • 强指针:_strong 默认情况,所有指针都是强指针

  • 弱指针:_weak

    • _weak Person *p = [[Person alloc] init] 错误写法,没有意义的写法,一创建就释放
  • ARC 特点

    • 不允许调用 release、retain、retainCount
    • 允许重写 dealloc,但是不允许调用[super dealloc]
    • @property 的参数
    • strong : 成员变量是强指针(适用于 OC 对象类型)
    • weak : 成员变量是弱指针(适用于 OC 对象类型)
    • assign:适用于非 OC 对象
  • 循环引用
    一端用:strong,另一端用:weak

  • 在Compiler Flags一列加上-fno-objc-arc就表示禁止这个.m文件的ARC

  • mrc 可以转 arc ,系统转换

19. copy

  • copy的基本原则
    • 因为拷贝要求修改原来的对象不能影响到拷贝出来得对象
    • 修改拷贝出来的对象也不能影响到原来的对象, 所以需要生成一个新的对象
    • 互不影响
  • copy的使用
    • 实现拷贝的方法有2个
      • copy:返回不可变副本
      • mutableCopy:返回可变副本
  • 普通对象实现拷贝的步骤
    • 遵守NSCopying协议
    • 实现-copyWithZone:方法
      • 创建新对象
      • 给新对象的属性赋值

20. Block

  • block访问外面变量
    • block内部可以访问外面的变量
    • 默认情况下,block内部不能修改外面的局部变量
    • 给局部变量加上__block关键字,这个局部变量就可以在block内部修改
  • 利用typedef定义block类型
    typedef int (^MyBlock)(int, int);
    // 以后就可以利用MyBlock这种类型来定义block变量
  • block是存储在堆中还是栈中
    • 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
    • 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
    • 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
    • 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
    • 如果是在ARC开发中就需要在前面加上__weak
    Person *p = [Person new];
    __weak Person *weakP = p;
    void (^myBlock) () = ^{
        
        weakP.age = 10;
    };
    myBlock();
    NSLog(@"age = %li", p.age);
    
    

21. Protocol

  • 协议

  • @protocol 协议名称 < NSObeject >
    // 方法声明列表….
    @end

  • 如何遵守协议

  • 类遵守协议
    @interface 类名 : 父类名 <协议名称1, 协议名称2>
    @end

  • 协议遵守协议
    @protocol 协议名称 <其他协议名称1, 其他协议名称2>
    @end

  • 协议中方法声明的关键字

  • @required (默认): 要求实现,如果没有实现,会发出警告

  • @optional: 不要求实现,不会有警告

  • 定义一个变量的时候,限制这个变量保存的对象遵守某个协议

  • 如果没有遵守对应的协议,编译器会警告

    类名<协议名称> *变量名;
    id<协议名称> 变量名;
    NSObject<MyProtocol> *obj;
    id<MyProtocol> obj2;
    
  • @property中声明的属性也可用做一个遵守协议的限制

    @property (nonatomic, strong) 类名<协议名称> *属性名;
    @property (nonatomic, strong) id<协议名称> 属性名;
    @property (nonatomic, strong) Dog<MyProtocol> *dog;
    @property (nonatomic, strong) id<MyProtocol> dog2;
    
  • 协议可用定义在单独.h文件中,也可用定义在某个类中

  • 如果这个协议只用在某个类中,应该把协议定义在该类中

  • 如果这个协议用在很多类中,就应该定义在单独文件中

  • 分类可用定义在单独.h和.m文件中,也可用定义在原来类中

  • 一般情况下,都是定义在单独文件

  • 定义在原来类中的分类,只要求能看懂语法

    原文作者:马文涛
    原文地址: https://www.jianshu.com/p/4bf9c326f34f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞