ARC

  • ARC(automatic reference counting),自动引用计数,编译器进行内存管理,降低程序崩溃,内存泄露。
  • NSObject (+alloc -retain -release -dealloc)
  • 自己生成对象,自己持有。(alloc、new、copy、mutableCopy),这些方法以及用这些方法开头的方法。非这几个或开头创建的对象使用autorelease,可以创建但不持有
  • copy、mutableCopy:利用NSCopying方法copyWithZone:方法和NSMutableCopying方法mutableCopyWithZone:方法。也生成并持有对象,区别在于生成的对象可变更和不可变更。
  • retain、release、autorelease.
  • autorelease,不立即释放,注册到autoreleasepool中,pool结束时释放。
  • 释放非自己持有的对象,会崩溃
  • -retainCount 获取引用计数,release之后计数为0会执行dealloc方法
  • oc实现,引用计数和对象内存地址存储在散列表里,通过散列表能找到各引用计数和对象的地址。
  • 废弃NSAutoreleasePool时,调用过autorelease方法的对象都将调用release方法。[pool drain]
  • NSRunLoop程序主循环,对NSAutoreleasePool对象生成、持有、释放
  • for循环可能会产生大量autorelease对象,可适当生成和释放NSAutoreleasePool对象
  • IMP Caching:取类名,方法名,函数指针。运行效率快
  • pool嵌套,使用的是最内层的pool执行autorelease
  • autorelease对象 实际是被加到pool对象的数组里,drain时会对数组中所有对象执行release
  • NSAutoreleasePool类方法showPools检查对象是否已经被autorelease
  • 不能对NSAutoreleasePool对象执行autorelease,会抛出异常,因为NSAutoreleasePool已经重载了autorelease实例方法
  • id相当于c语言中的void *
  • __strong、__weak、__unsafe_unretained、__autoreleasing 4种修饰符
  • __strongARC有效时默认修饰符,不写就默认有,id __strong obj,:(id __strong)obj(做参数时)
  • id __strong obj;等同于id __strong obj = nil; __weak、__autoreleasing 也同样
  • __weak是解决循环引用问题的,循环引用会导致内存泄露,内存泄露就是对象超出生存周期依然存在
  • 自引用,持有自身。
  • 使用__weak不会持有对象。直接用__weak引用编译器会告警,应该先用__strong引用,再赋值给__weak变量
  • __weak引用的对象被释放,则其修饰的变量会自动置为nil
  • __unsafe_unretained__weak在iOS5以下版本的代替
  • __unsafe_unretained也不持有对象,直接使用会告警。其引用的对象被释放,其修饰的变量不会自动置为nil。所以访问__unsafe_unretained修饰的变量,有可能会崩溃,需要确保其引用的对象存在,才没问题。
  • ARC有效时,不能使用autorelease方法,也不能用NSAutoreleasePool类。
  • ARC有效时,用@autoreleasepool {} 块替代NSAutoreleasePool类生成持有废弃。用__autoreleasing修饰变量代替调用autorelease方法。显示使用__autoreleasing同显式使用__strong罕见
  • 编译器检查方法名是否以(alloc、new、copy、mutableCopy)开头,决定是否将返回值的对象注册到自动释放池。
  • 在访问__weak修饰的变量时,实际会将要访问的对象注册到自动释放池,以防因为是弱引用,而对象又被废弃了
  • id的指针或对象的指针,id *objNSObject **obj,实际会被附上__autoreleasing修饰符变成id __autoreleasing *objNSObject* __autoreleasing *obj
  • NSError *error = nil; &error 等同于 NSError **类型
  • 使用__autoreleasing修饰符的变量作为参数,也会注册到自动释放池
  • 赋值给对象指针时,所有权修饰符必须一致。NSError *error = nil; NSError * __strong *pError = &error; 不加__strong编译器会报错。同理,__weak__unsafe_unretained也一样NSError __weak *error = nil;NSError * __weak *pError = &error;
  • 非显式声明的__strong变量作为方法参数中的对象指针类型,会被编译器自动转换为__autoreleasing
  • 对象指针作为参数虽然可以声明为__strong类型,但最好不要这样做,因为这不符合内存管理的思考方式。还是以__autoreleasing
  • __autoreleasing修饰变量,必须为自动变量(局部变量、函数、方法参数)
  • @autoreleasepool {} 也能嵌套使用,ARC无效其也能使用,推荐用@autoreleasepool
  • NSRunLoop实现,随时能释放自动释放池中的对象
  • _objc_autoreleasePoolPrint(),调试用的非公开函数
  • ARC下,不能用retain/release/retainCount/autorelease.使用会出现编译错误,dealloc也是
  • ARC下,除(alloc、new、copy、mutableCopy)还有init需返回自己持有的对象。特例 - (void)initialize;
  • dealloc适用于释放c语言库分配的内存、删除代理、观察者对象。
  • ARC下,dealloc里不能调用 [super dealloc]
  • oc对象不能作为c语言结构体成员,会引起编译报错,如果要用,可转为void *或加__unsafe_unretained修饰,因为其不属于编译器的内存管理对象,但用这个有隐患,可能造成程序崩溃或内存泄露
  • ARC下,void * 和id转换,需要用__bridge,void *p = (__bridge void*)obj;id o = (__bridge id)p,这样转成void*安全性也很低,例如空指针导致崩溃
  • __bridge_retained赋值时增加持有对象,与retain相似
  • __bridge_transfer赋值时释放持有对象,与release相似
  • 这些转换多数用于oc对象与core foundation(c语言编写框架)对象转换时
  • CFRetain/CFRelease,CFBridgingRetain()和__bridge_retained一样的作用,CFBridgingRelease和__bridge_transfer一样的作用,做转换时必须恰当使用
  • 属性修饰中,copy、retain、strong 相当于给使用__strong修饰符的变量赋值。assign、unsafe_unretained 相当于__unsafe_unretained,weak相当于__weak
  • 属性中带copy不是简单的赋值,是复制对象
  • C语言中使用动态数组,id __strong *array = nil; __strong必要显示声明,不显示默认为__autoreleasing,初始化必须为nil,指针类型不会自动置nil。分配内存用calloc函数,可使分配区域初始化为0(malloc函数还需用memset函数将内存填充为0,malloc不可将数组元素置nil,可能导致释放不存在的对象,推荐用calloc函数),释放时还必须释放每个数组元素,不能仅释放数组内存。动态数组不归编译器管,静态数组归编译器管。需要将数组元素置nil再释放数组。memcpy和realloc也不要用,会引起内存泄露和重复废弃。__weak可修饰动态数组,__autoreleasing不可,__unsafe_unretained可用于C语言的指针
  • objc_retainAutorelesedReturnValue和objc_autoreleaseReturnValue函数不将对象注册到自动释放池直接传递,最优化
  • __weak修饰的变量引用的对象如果被释放,则变量自动置nil.使用__weak修饰的变量,会自动注册到自动释放池
  • weak散列表,objc_storeWeak函数
  • 如大量使用__weak修饰符的变量,会消耗cpu资源,所有只在要避免循环引用时使用
  • __weak不能修饰刚创建的对象,因为会立即被释放,并将变量置nil
  • __weak修饰的变量被使用时,实际会先retain对象,并注册到自动释放池。多次使用多次注册,最好先赋值给__strong修饰的变量再用
  • NSMachPort类不支持__weak,有自己的引用计数机制,使用的话编译器会报错
  • allowsWeakReference返回NO程序将异常终止、retainWeakReference返回NO,将无法获取__weak引用的对象,将返回nil
  • _objc_rootRetainCount函数获取计数数值,但有时是不准确的,多线程竞争状态、对象被释放、不正确的地址等

Blocks

  • 带局部变量值的匿名函数
  • 范式:^ 返回值类型 参数列表 表达式
  • 返回值类型可省略,要么是表达式return语句返回值的类型,要么是void类型
  • 参数列表可省略,使用参数时,^{}(2个都省略)
  • 函数指针:
    int func(int count) { return count + 1; } int (*funcptr)(int) = &func;
    函数指针调用并赋值:int result = (*funcptr)(10);
  • blockl类型变量:int (^blk)(int) = ^(int count){return count+1;}; int (^blk1)(int) = blk;
  • 可做函数参数、返回值使用,记述方式复复杂,可用typedef解决
  • typedef int (^blk_t)(int); void func(int (^blk)(int))变为void func(blk_t blk) int (^func())(int) 变为blk_t func()
  • block类型变量指针:blk_t *blkptr = &blk; (*blkptr)(10);
  • block中使用局部变量,截获的是局部变量的瞬间值。即使这些值后面被改也不影响block截获的值
  • block表达式中改写外部变量的值,会产生编译错误。若想改,必须给外部变量声明__block说明符__block int val = 0;
  • 向截获的外部可变数组中插值,不会有问题,但向该数组赋值会有问题
  • block表达式中使用c语言数组,例如cosnt char text[] = "hello";使用text[2]有问题,使用指针可以,cosnt char *text = "hello";再使用text[2]可以
  • objc_msgSend函数,从对象持有类的结构体中检索函数指针并调用
  • block实质是oc对象,block调用实质是用函数指针调用函数
  • id、Class都是结构体指针
  • oc类与对象的实质:类的结构体指针,class_t包含成员变量、方法名、方法实现、属性、父类指针
  • block的局部变量截获,只针对表达式中使用了的变量,没使用的不截获
  • 截获的实质是,执行block语法时,表达式使用的变量被保存到block的结构体实例
  • c语言中的静态变量、静态全局变量、全局变量,允许block改写值 static
  • static表示作为静态变量存储在数据区中 auto表示作为自动变量存储在栈中
  • __block,会使变量转为结构体实例。多个block中可使用同一个__block变量,同一个block中使用多次__block变量
  • block可存储在栈上、堆上、数据区上(.data区)。在记述全局变量的地方使用block语法,就是存储在数据区上。另一个,block不截获自动变量时,也可以存储在数据区。
  • 除去上两种之外,存储在栈上。栈上的block,变量作用域结束,block就该被废弃。但可以通过复制到堆上,即使变量作用域结束,堆上的block和__block变量还可以继续存在。
  • __block变量的结构体成员变量__forwarding可以实现__block变量配置在堆上和栈上都能正确访问__block变量
  • ARC下,将block作为函数返回值返回时编译器会自动生成复制到堆上的代码
  • 将block作为方法参数使用,编译器不能自动判断生成代码。
  • cocoa框架的usingBlock和gcd中的api不需要手动复制
  • 需要手动复制的时候,给block 调 copy 方法就行。不能将所有栈上的block都复制到堆上,因为在栈上也能使用的block复制到堆上,会浪费cpu资源
  • 存在数据区的block调copy什么也不做,堆上的block调copy方法,引用计数加1
  • 多次调copy也没有问题,不确定是否要copy时,copy一下不会有任何问题
  • __block变量跟随block被从栈复制到堆,并被block持有,被多个block持有,增加__block变量的引用计数
  • 栈上和堆上同时有__block变量,在block内部和外部访问__block变量,访问的是同一个,即堆上的__block变量,栈上的__forwarding指向堆上的结构体实例地址
  • __main_block_copy_0 和 __main_block_dispose_0 函数,是block中的相当于retain和release作用的函数,block从栈复制到堆时调copy,堆上的block被废弃时调dispose
  • block从栈复制到堆的几种情况:调用block的copy方法,block作为函数返回值,赋值给类的__strong的id类型或block类型变量,方法名带usingBlock的方法里传递block时。这4种实际上都是调用_Block_copy函数,从栈复制到堆
  • __block和持有__strong修饰的对象不同之处,参数不同,BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF
  • 调用copy才会持有截获的对象,不然对象无法超出变量作用域而存在
  • __block和__strong一起使用,作用的__block一样。__block和__weak一起使用,作用域结束时,__weak修饰的对象会被置为nil。__unsafe_unretained__block一起使用,注意空指针,__block不可与__autoreleasing一起使用,会报编译错误
  • block的循环引用,类成员变量是block类型,block中持有类的对象,可以通过__weak__unsafe_unretained避免循环引用,注意引用了类的其他成员变量,也会造成循环引用
  • 使用__block也可避免循环引用,但要在block语句中执行置nil操作,且要执行block

GCD

  • serial queue 和 concurrent queue,串行和并行队列
  • 多个serial queue可并行执行
  • 线程过多会降低系统的响应性能
  • 仅在多个线程更新相同资源导致数据竞争时使用serial queue
  • serial queue比concurrent queue能生成更多的线程
  • 生成concurrent queuedispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
  • 生成serial queue dispatch_queue_create("", NULL)
  • dispatch_queue_t 类型
  • 队列必须自己释放,dispatch_release(),dispatch_retain()
  • dispatch_async()追加的block,即使执行dispatch_release(),也不会立即废弃队列,因为block也持有队列,执行完block才会废弃
  • 含有create的api,有必要释放
  • main dispatch queue 是 serial queue,在主线程的runLoop中执行
  • global dispatch queue 4个优先级,高high、默认default、低low、后台background
  • dispatch_get_main_queue() dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0)
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0)
  • 系统级的main dispatch queue和global dispatch queue 不需要retain\release
  • create生成的queue使用global queue默认优先级相同优先级的线程,要改变优先级用dispatch_set_target_queue(queue1,queue2) queue1是欲设置优先级的队列,queue2是同优先级的目标队列,queue1需要为create生成的
  • dispatch_set_target_queue还会改执行层次,多个serial queue指定同一个serial queue,并行执行的多个serial queue会在目标queue上同时只执行一个
  • dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull * NSEC_PER_SEC); dispatch_after(time,dispatch_get_main_queue(),^{});
  • runloop每隔1/60秒执行,这个时间延迟只是大致,不精确
  • ull是c语言数值字面量,表示unsigned long long
  • NSEC_PER_SEC单位是秒,NSEC_PER_MSEC单位是毫秒
  • dispatch_time_t 类型的值也可由NSDate对象转化来,使用struct timespec类型以及dispatch_walltime()函数,见155页
  • dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{}); dispatch_group_nofity(group,dispatch_get_main_queue(),^{}); dispatch_release(group);
  • 用group可以处理多个并行执行,监测结束做结束处理
  • group带create结束需要释放,追加的block也持有group,所以group使用结束立即释放没问题
  • dispatch_group_wait函数,dispatch_group_wait(group,DISPATCH_TIME_FOREVER);第二参数为等待时间属于dispatch_time_t类型,这里为永久等待。
  • long result = dispatch_group_wait(group,time);返回值不为0,意味着未结束,二参为DISPATCH_TIME_FOREVER时,结果恒为0。wait函数会阻塞线程,参数为NOW时,不等待
  • dispatch_barrier_async(queue, blk);
  • 使用concurrent queue和barrier函数,可实现数据库访问和文件访问,barrier相当于插入到并行队列中的单个处理,会等待插入之前的处理全部结束,然后单独执行barrier处理,之后再继续插入之后的并行处理
  • dispatch_sync函数,也会阻塞线程
  • 死锁问题:dispatch_sync(mainQueue, ^{}); dispatch_async(mainQueue,^{ dispatch_sync(mainQueue, ^{}); });

dispatch_async(serialQueue,^{ dispatch_sync(serialQueue, ^{});});

  • dispatch_apply(10,queue,^(size_t index){})执行多次,并等待全部执行结束。在dispatch_async中使用使用apply,162页
  • dispatch_suspend(queue); dispatch_resume(queue); 挂起对已执行的处理没影响,未执行的处理会停止,恢复会继续执行
  • dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);计数为0时等待,大于0减1不等待。
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)等待计数大于等于1,减1并从函数返回。返回不为0等待,为0继续。
  • dispatch_semaphore_signal(semaphore);给计数加1,处理前等待,处理后+1
  • static dispatch_once_t pred; dispatch_once(&pred,^{}); 生成单例时使用
  • 178页代码