美文网首页
神书《Objective-C高级编程》 学习笔记 (二)

神书《Objective-C高级编程》 学习笔记 (二)

作者: 萨缪 | 来源:发表于2020-04-27 22:31 被阅读0次

    接着昨天的来:

    16.weak修饰符另一个优点:在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且处于nil被赋值状态(空弱引用)。
    所以这个功能的作用就是:通过检查附有__weak修饰符的变量是否为nil可以判断被赋值的对象是否已废弃。

    __autorelease修饰符:

    17.ARC有效时,要通过将对象赋值给附加了_autoreleasing修饰符的变量等价于在ARC无效时,对象被注册到autorelease
    也可以理解为ARC有效时,用__autorelease修饰符的变量替代autorelease方法

    18.又差点忘的一个知识点:
    strong修饰符会自动进行release操作!所以alloc/new/copy/mutableCopy方法来取得对象 这些方法都是强引用,相当于strong,不会自动将返回值的对象注册到autoreleasepool.
    所以这里就引申出编译器的另一个功能:编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool 是的话就是上面说的

    19.下面有一个很有意思的调用:

    @autoreleasepool {
    //取得非自己生成并持有的对象
    id __strong obj = [NSMutableArray array];
    //因为变量obj为强引用,所以自己持有对象
    //由编译器判断其方法名后,自动注册到autoreleasepool中
    }
    离开大括号后:
    obj超出其作用域,强引用失效 所以自动释放自己持有的对象

    同时,随着@autoreleasepool块的结束 注册到@autoreleasepool块中的所有对象都被自动释放
    但是!!!因为对象的所有者不存在 所以废弃对象

    20.一个疑问:

    • (id)array
      {
      id obj = [[NSMutableArray alloc] init];
      return obj;
      }

    因为该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool。
    那么注册进去之后呢?是用的时候取出来还是?
    是会一直在pool中吗?什么时候被销毁啊
    emmm答案就是 假如在@autoreleasepool 里面这样使用该方法:
    id __strong obj = [NSMutableArray array];

    那么在执行完array方法后 现在obj被注入自动释放池了,然后程序返回
    id __strong obj = [NSMutableArray array];

    这一行 现在现在obj已被注入自动释放池 那么什么时候释放呢?那当然是随着autoreleasepool被销毁后一起释放啊!

    21.在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool中的对象,比如:

    id __weak obj1 = obj0;
    id __autoreleaseing tmp = obj1;
    //就是想要访问 obj1 就得用有__autoreleaseing属性的对象来访问

    原因是:因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。如果把要访问的对象注册到autoreleasepool中(id __autoreleaseing tmp = obj1;)

    那么在@autoreleasepool块结束之前都能确保该对象存在

    22.所以对__autoreleaseing属性总结下来就是:
    使用附有__autoreleaseing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy之外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象

    23.对象指针必须附加__strong修饰符,保证其所有权修饰符必须一致:

    NSError * error = nil;
    NSError * __strong * pError = &error;
    //这里用strong修饰是因为 什么修饰符都不添加 默认error也用strong来修饰

    24.调用非公开函数_objc_autoreleasePoolPrint()函数可以打印出自动释放池中的内容,有效地帮我们调试 注册到autoreleasepool上的对象

    25.在编写程序的过程中,在ARC有效的情况下,它会遵守一定的规则

    1、不能使用retain/release/retainCount/autorelease。

    2、不能使用NSAllocateObject和NSDeallocateObject
    的。

    3、须遵守内存管理的方法命名规则

    4、不要显式调用dealloc

    5、使用@autoreleasepool块替代NSAutoreleasePool

    6、不能使用区域NSZone

    7、对象型变量不能作为C语言结构体(struct/union)的成员

    8、显式转换“id”和“void”

    第一项:"不能使用retain/release/retainCount/autorelease。" 很明显,当程序使用了ARC进行编程的话,是禁止键入retain/release代码的,因为ARC有效时,调用了retain/release会造成编译器错误。
    使用retain/release/retainCount/autorelease方法只有在ARC无效且手动进行内存管理的情况下使用

    第二项:"不能使用NSAllocateObject和NSDeallocateObject的" 其实

    id objc = [[NSObject alloc]init];

    苹果内部是直接调用NSAllocateObject来生成并持有对象的,他同retain一样,只有在没有使用ARC的情况下才可以使用。如果在ARC有效时使用了的话,会造成编译器错误,提示:not available in automatic reference mode.

    第三项:"须遵守内存管理的方法命名规则" 在ARC无效的时候,用于对象生成/持有的方法必须遵守以下的命名规则
    alloc
    new
    copy
    mutableCopy

    以上述名称开始的方法,应当返回调用方所应当持有的对象,这在ARC有效时也同样有效,只不过在ARC有效时,会更加的严格。并且,在ARC有效时,还需要再添加一条命名规则:

    init

    以init开始的方法的规则,要比alloc\new/copy/mutableCopy更加的严格,该方法必须是实例方法,而且必须是返回对象。返回的类型必须是id类型或者该方法声明类的对象类型,亦或者是该类的超类型或者子类型。该返回对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化处理并返回对象。

    例如:

    id objc = [[NSObject alloc]init];
    方法的命名规则可以是:

    -(id)initWithObject:(NSObject *)obj;

    像下面这种命名规则是错误的,因为他没有返回对象:

    -(void)initWithObject:(NSObject *)obj

    第四项:"不要显式调用dealloc" 无论ARC是否有效,只要是对象的所有者不在持有该对象,该对象就会被废弃,对象被废弃时,无论ARC是否有效,都会调用dealloc方法

    -(void)dealloc

    {

    /*

    *此处是对象被废弃的时候,要实现的代码

    */

    }
    就如使用C语言库的时候,需要使用如下方法释放内存

    -(void)dealloc

    {

    free(buffer_);

    }
    其实dealloc方法,在大部分情况下还适用于删除已注册的代理或者观察者

    -(void)dealloc

    {

    [[NSNotificationCenter defaultCenter]removeObserver:self];

    }

    在ARC无效的时候,还应该注意下面一点,

    /*

    *在ARC无效的时候,必须调用[super dealloc]

    */

    -(void)dealloc

    {

    [super dealloc];

    }
    需要注意的是:ARC有效时 无法显式调用dealloc这一规则,如果使用就会同release方法一样 引起编译错误

    第五项:"使用@autoreleasepool块替代NSAutoreleasePool" 在ARC有效的时候,调用NSAutoreleasePool会造成编译器出错

    第六项:"不能使用区域NSZone" "NSZone"无论ARC是否有效,区域在现在的运行时系统(编译器宏OBJC2)中已经单纯的被忽略;

    第七项:"对象型变量不能作为C语言结构体(struct/union)的成员" 这句话其实就是,ARC有效时,在结构体struct内部不能出现对象,如下编译器就会报错

    struct data{

    NSArray * arr;
    

    };

    报错如下:ARC forbids Objective-C objects in struct

    26._unsafe_unretained修饰符以外的 __strong/ __weak/ __autoreleasing修饰符保证其指定的变量初始化为nil.

    同样的,附有__strong/ __weak/ __autoreleasing修饰变量 的数组也能保证其初始化为nil

    27.数组:
    像下面这样将nil代入到malloc函数所分配的数组各元素中来初始化是非常危险的:

    array = (id __strong *)malloc(sizeof(id) * entries);
    for (NSUInteger i = 0; i < entries; ++i)
    array[i] = nil;

    原因是:比较难懂的一句话:

    这是因为由malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给附有__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。
    

    28.数组:
    在静态数组中,编译器能够根据变量的作用域自动插入释放赋值对象的代码,而在动态数组中,编译器不能确定数组的生存周期,所以无从处理。
    如下所示:一定要将nil赋值给所有元素中,使得元素所赋值对象的强引用失效,从而释放那些对象。在此之后,使用free函数废弃内存块

    for (NSUInteger i = 0; i < entries; ++i)
    array[i] = nil;

    free(array);

    ARC的实现:
    29.__strong修饰符

    赋值给附有__strong修饰符的变量在实际的程序中到底如何运行?

    {
    id __strong obj = [[NSObject alloc] init];
    }

    该源代码实际上可转换为调用以下函数:

    //编译器的模拟代码
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    objc_release(obj);

    注意两次调用objc_msgSend方法(alloc 和 init)

    30.weak修饰符:

    若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
    
    使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象(我们通过下面这段代码来确定这个功能)
    

    id __weak obj1 = obj;
    NSLog(@"%@"m obj1);

    将被分解成以下代码 (模拟代码)

    /* 编译器模拟代码 */
    id obj1;
    objc_initWeak(&obj1, obj);
    id tmp = objc_loadWeakRetained(&obj1);
    objc_autorelease(tmp);
    NSLog(@"%@", tmp);
    objc_destroyWeak(&obj1);

    __weak同引用计数一样通过散列表(哈希表)实现,大致流程如下:

    1.objc_initWeak(&obj1, obj)函数初始化__weak修饰的变量,通过执行objc_storeWeak(&obj1, obj)函数,以第一个参数(变量的地址)作为key,把第二个参数(赋值对象)作为value存入哈希表。
    2.由于弱引用不能持有对象,函数objc_loadWeakRetained(&obj1)取出所引用的对象并retain。
    3.objc_autorelease(tmp)函数将对象注册到autoreleasepool中。
    4.objc_destroyWeak(&obj1)函数释放__weak修饰的变量,通过过程执行objc_store(&obj1, 0)函数,在weak表中查到变量地址并删除。废弃对象调用objc_clear_deallocating函数,这个过程会将weak表记录中__weak修饰的变量地址赋值为nil。

    同时,前面的源代码与下列源代码相同

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

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

    对象的释放过程:

    objc_release
    引用计数为0, 执行dealloc
    _objc_rootDealloc
    object_dispose
    objc_destructInstance
    objc_clear_deallocating

    objc_clear_deallocating执行动作:

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

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

    31.很有意思的实现:

    {
    id __weak obj = [[NSObject alloc] init];
    }

    该源代码将自己生成并持有的对象赋值给附有__weak修饰符的变量中,所以自己不能持有该对象,这时会被释放并被废弃,引起编译警告。
    那么编译器是怎么处理这行代码的呢?

    //编译器的模拟代码
    id obj;
    id tmp = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(tmp,@selector(init));
    objc_initWeak(&obj, tmp);
    objc_release(tmp);//tmp自己不能持有对象 需要被释放并废弃
    objc_destoryWeak(&object);

    虽然自己生成并持有的对象通过objc_initWeak函数被赋值给附有__weak修饰符的变量中,但weak不能持有,编译器判断其没有持有者,故该对象立即通过objc_release函数被释放和废弃
    这样一来,nil就会被赋值给引用废弃对象的附有__weak修饰符的变量中

    32.一个总结:
    因为附有 __weak修饰符变量所引用的对象被注册到autoreleasepool中,所以在@autoreleasepool块结束前都可以放心使用。但是,如果大量的使用附有__weak修饰符的变量,注册到autoreleasepool的对象数量也会大大的增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用。

    如下所示:

    {
    id __weak o = obj;
    id tmp = 0;
    NSLog(@"1 %@", tmp);
    NSLog(@"2 %@", tmp);
    NSLog(@"3 %@", tmp);
    NSLog(@"4 %@", tmp);
    NSLog(@"5 %@", tmp);
    }

    在 tmp = 0 时 对象仅登录到autoreleasepool一次
    但如果不加 tmp这个变量而是直接打印 o 那么o所赋值的对象就会被注册到autoreleasepool五次

    33.__autoreleasing的实现
    _autoreleasing修饰变量,等同于ARC无效时对象调用autorelease方法。

    @autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
    }

    /* 编译器的模拟代码 */
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    objc_autorelese(obj);
    objc_autoreleasePoolPop();

    34.不使用_autoreleasing修饰符,仅使用附有__weak声明的变量也能将引用对象注册到autoreleasepool中

    相关文章

      网友评论

          本文标题:神书《Objective-C高级编程》 学习笔记 (二)

          本文链接:https://www.haomeiwen.com/subject/ukppwhtx.html