Objective-C高级编程 要点记录

  • 内存管理
  • Block
  • GCD

内存管理

  1. 自己生成的对象,自己所持有 (alloc/new/copy/mutableCopy)
  2. 非自己生成的对象,自己也能持有, 如retain方法
  3. 不再需要自己持有的对象时释放, 使用release方法
  4. 无法释放非自己持有的对象

autorelease的具体使用方法
1. 生成并持有NSAutoreleasePool对象
2. 调用已分配对象的autorelease实例方法
3. 废弃NSAutoreleasePool对象

ARC有效时,id类型和对象类型必须加上所有权修饰符,一共有四种:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

使用__weak来避免循环引用,__weak不能持有对象实例,另外在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值的状态(空弱引用)

__unsafe_unretained是不安全的所有权修饰符,尽管ARC式的内存管理编译器的工作,但附有 __unsafe_unretained修饰符的变量不属于编译器的内存管理对象,与__weak一样,也无法持有对象

在ARC有效的情况下,必须遵守一定的规则
1. 不能使用retain/release/retainCount/autorelease
2. 不能使用NSAllocateObject/NSDeallocateObject
3. 须遵守内存管理的方法命名规则
4. 不要显式调用dealloc,不能使用[super dealloc]
5. 使用@autoreleasepool 块代替NSAutoreleasePool
6. 不能使用区域(NSZone)
7. 不对象型变量不能作为C结构的成员,除非加__unsafe_unretained
8. 显式转换”id”和”void*”, 使用__bridge, __bridge_transfer, __bridge_retained

使用__weak

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
  • 使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象

id __weak obj1 = obj; 将被分解成以下代码 (模拟代码)

id obj1 = 0; objc_storeWeak(&obj1, obj); objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二个参数赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中(哈希表),如果第二参数为, 则把变量的地址从weak表中删除。由于一个对象可同时赋值给多个附有__weak修饰符的变量中,所以对于一个键,可注册多个变量的地址。

对象的释放过程:

  1. objc_release
  2. 引用计数为0, 执行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

objc_clear_deallocating执行动作:

  1. 从weak表中获取废弃对象的地直为键的记录
  2. 将包含在记录中的所有附有__weak修饰符变是的地址,赋为nil
  3. 从weak表中删除该记录
  4. 从引用计数表中删除废弃对象的地址为键的记录
  5. 如果有大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源,良策是只在需要避免循环引用的时候使用__weak,可先将__weak修饰的变量赋值给__strong修饰的变量

独自实现引用计数的类无法使用__weak,如NSMachPort,另外当allowsWeakReference/retainWeakReference方法返回No的时候也无法使用

Block

Block是带有自动变量(局部变量)的匿名函数
Block格式: ^返回值类型 参数列表 表达式

Block可省略返回值类型,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型。若有多个return,所有return返回值类型必须相同

如果不使用参数,Block可以省略参数列表

Blocks中表达式所截获所使用的自动变是的值,即保存该自动变量的瞬间值,因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使修改Block中使用的自动变量的值也不会影响Block执行时自动变量的值。

若想在Block语法中的表达式中将值赋给在Block语法外声明的自动变量,需要在该变量前加上__block说明符。

Block的实现:^(blk)(void) = ^{printf(“Block\n”);};

struct __block_impl
{
    void* isa;              //block类型
    int Flags;              //标志
    int Reserved;           //保留位
    void* FuncPtr;          //函数指针
}

struct __main_block_desc_0
{
    size_t reserved;        //保留位
    size_t Block_size;      //Block大小

}

//如果Block使用了自动变量,则自动变量会相应的追加到该结构体中, 并在构造函数中
//进行初始化
struct __main_block_impl_0
{
    struct __block_iml impl;            //Block实现结构
    struct __main_block_desc_0* Desc;   //Block描述结构

}

//block 代码块生成一个静态函数, __cself类似于self,通过该指针可以拿到里面的自动变量
static void __main_block_func_0(struct __main_block_impl_0* __cself)
{
    printf(“Block\n”);
}

int main(void)
{
//将静态函数赋值给FuncPtr函数指针
    void (*blk)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));

    //调用block, 调用结构中的函数指针
    ((void*)(__block_impl*))((__block_impl*)blk)->FuncPtr)((__block_impl*)blk);

}

静态变量/静态全局变量/全局变量 可在Block中直接修改,静态变量则是使用其指针对其进行访问

为什么自动变量不通过指针来访问?
答: 在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,所以Block中超过变量作用域而存在的变量同静态变量一样,不能通过指针访问原来的自动变量。通俗的说,就是Block无法确定在何时被调用,如果通过指针指向,那么当自动变量销毁之后再进行访问,将导致程序崩溃。

Block的三种类型

类型 设置对象的存储域 复制效果
NSConcreteStackBlock 从栈复制到堆
NSConcreteGlobalBlock 程序的数据区域 什么也不做
NSConcreteMallocBlock 引用计数增加

不管Block配置在何处,使用copy方法复制都不会引起任何问题,在不确定时调用copy方法即可。

Block提供了将Block和__block变量从栈上复制到堆上的方法

栈上的Block复制到堆上的情况:

  • 调用Block的copy实例方法
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递的Block时

使用__block变量避免循环引用的优点:

  • 通过__block变量可控制对象的持有时间
  • 在不能使用__weak的环境中不使用__unsafe_unretained即可,不必担心垂悬指针,在执行Block的时可动态决定是否将nil或其他对象赋值在__block变量中。
    使用__block变量避免循环引用的缺点:
  • 为避免循环引用,必须执行Block

GCD

Dispatch Queue的种类 说明
Serial Dispatch Queue(串行) 等待现在执行处理结束
Concurrent Dispatch Queue(并形) 不等待现在执行处理结束

创建Dispatch Queue的方法
1. dispatch_queue_create (DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT),通过该方法生成的queue,需要调用dispatch_release来释放
2. 获取系统标准提供的Main Dispatch Queue/Global Dispatch Queue

变更生成的Dispatch Queue的优先级应使用dispatch_set_target_queue

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.exmaple.gcd.MySerialDispatchQueue”, NULL);
dispatch_queue_t globalDispatchQueueBackground = dispach_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

dispatch_set_target_queue无法指定Main Dispatch Queue和Global Dispatch Queue

dispatch_after函数并不是在指定时间后执行处理,而是在指定时间后追加处理到Dispatch Queue.

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,若只使用Serial Dispatch Queue,只需将处理追加到最后即可,若是使用Concurrent Dispatch Queue或同时使用多个DispatchQueue时,则需使用Dispatch Group

dispatch_group_wait等待Dispatch Group中所有处理结束,若第二个参数设置为DISPATCH_TIME_FOREVER,则将永远等待。即只要属于Dispatch Group的处理尚未结束,就会一直等待,中途不能取消,若该函数返回值为0,表示所有处理执行结束,否则表示Dispatch Group中仍有处理未执行完毕

dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中,然后再由dispatch_barrier_async函数追加的处理执行完毕后,再Concurrent Dispatch Queue才恢复为一般的动作.

dispatch_sync函数不建议使用,因为使用该函数容易引起死锁

dispatch_apply按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理结束

dispatch_suspend/dispatch_resume 挂起/恢复指定的Dispatch Queue

Dispatch Semaphore 是持有计数的信号量,该计数是多线程编程中的计数类型信号,通过dispatch_semaphore_create创建,dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值>= 1,然后计数值-1,并从dispatch_semaphore_wait函数返回,使用dispatch_semaphhore_signal可将计数值+1

dispatch_once函数是保证在应用程序执行中只执行一次指定的处理

Dispatch Source的种类

名称 内容
DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加
DISPATCH_SOURCE_TYPE_DATA_OR 变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 检测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ 可读取文件映像
DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像
DISPATCH_SOURCE_SIGNAL 接收信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更

Dispatch Queue没有“取消”这一概念,一旦将处理追加到Dispatch Queue中,就没有方法可以将该处理去除,也没有方法可以在执行中取消该处理,即要么放弃取消,要么使用其他方法.

Dispatch Source可以取消, 而且取消时必须执行的处理可指定为回调用的Block形式.

你可能感兴趣的:(iOS开发)