• ARC的一些基础概念


    先回顾一下Objective-C类的定义格式:

    MyClass.h
    #import <Foundation/Foundation.h>
    
    @interface MyClass : NSObject
    {
        //成员变量定义,没有修饰符的情况下,默认为@protected
        int i;
        
        @private
        float f;
        char *c;
        
        @protected
        NSString *str;
    
        @public
        id obj;
        struct {
                unsigned int lineBreakMode:3;
                unsigned int highlighted:1;
                unsigned int baselineAdjustment:2;
            } _textLabelFlags;
    }
    
    //属性定义
    @property (nonatomic,copy) NSString *name;
    @property (strong, nonatomic) IBOutletUILabel *lblName;
    
    //方法定义
    //Objective-C的编译器会给没有写显式的返回值函数加上一个默认的返回值,它的类型是id
    - promoteTo:newPosition; 
    - (void)setTitle:(NSString *)string;
    + (id) getMyName:(NSString *)string;
    
    @end
    MyClass.m
    #import "MyClass.h"
    
    @implementation MyClass
    
    @synthesize name;
    @synthesize lblName;
    
    - (id)promoteTo:(id)newPosition
    {
        returnnil;
    }
    
    - (void)setTitle:(NSString *)string
    {
    }
    
    +(id)getMyName:(NSString *)string
    {
        return@"myName";
    }
    
    @end

    定义成员变量作用域

    (1) @protected -- 作用范围在自身类和继承自己的子类,默认。

    (2) @private -- 作用范围只在自身类。

    (3) @public -- 范围最广,可被任何类访问

    实例化一个类的一般做法是:[[Class alloc] init]。这个操作其实做了两件事:alloc给对象分配内存空间,init对对象进行初始化。 

    上面的这个类,当外部实例化它时:

    MyClass *tmpClass = [[MyClass alloc] init];
    
    NSLog(@"%s",tmpClass->c); //error,"Instance variable 'c' is private
    
    NSLog(@"%d",tmpClass->i); //error,"Instance variable 'i' is protected
    
    id tmp = tmpClass->obj; // no-warning
    
    UILabel *tmpLabel = tmpClass.lblName; // no-warning

    可以看到成员变量只有声明为@public时,才能以"->"的形式访问。而要以"."操作符来访问变量,则必须声明为@property。

    @property的一个主要作用就是生成getter和setter方法,这样做简化了大部分代码,看下面的例子:

    @interface Worker : NSObject
    {
        NSString *_name;
    }
    
    - (NSString*)name;
    
    - (void)setName:(NSString*)strName;
    
    //等价于
    //@property (nonatomic,copy) NSString *name;

    在实现文件(Worker.m)里:

    - (NSString*)name
    {
        return _name;
    }
    
    - (void)setName:(NSString*)strName
    {
        if(_name!=strName)
        {
            [_name release];
            _name = [strName copy];
        }
    }
    
    //等价于
    //@synthesize name;

    上面的代码解释了@property和@synthesize背后的实际操作,同时也能看出来@property(copy)代表_name = [strName copy]; 如果你指定为retain,代表_name = [strName retain]; 指定为assign,代表_name = strName;

    所以,@property并不仅仅是做了一个简化代码的getter/setter操作,同时它还做内存管理。

    声明@property的语法为:@property(参数1, 参数2) 类型 名字。其中参数主要分为三类:

    读写属性:readwrite(默认)/readonly

    setter语意:assign(默认)/retain/copy

    原子性:atomic(默认)/nonatomic

    各参数意义如下:

    readwrite:产生setter/getter方法

    readonly:只产生getter,不产生setter

    assign:默认类型,直接赋值而不进行retain操作

    retain:先release旧值,再retain新值

    copy:进行copy操作,与retain一样

    atomic:开启多线程变量保护,会消耗一定的资源

    nonatomic:禁止多线程变量保护,提高性能

    先来研究一下readwrite和readonly:

    【示例一】

    #import <Foundation/Foundation.h>
    
    @interface NewClass : NSObject
    
    //这里声明为readonly,所以只会生成getter方法
    @property (nonatomic,readonly,copy) NSString *nickname;
    
    @end

    基于上面的头文件,当你synthesize name以后,尝试做如下操作:

    NewClass *myClass = [[NewClass alloc] init];
    
    myClass.nickname = @"abc";

    这个时候编译器会报错:Assigning to property with 'readonly' attribute not allowed. 就是提示你对设置为readonly的属性赋值是被禁止的。

    【示例二】

    #import <Foundation/Foundation.h>
    
    @interface NewClass : NSObject
    
    //这里声明为readonly,所以只会生成getter方法
    @property (nonatomic,readonly,copy) NSString *nickname;
    
    //提供了一个自定义的setter方法
    - (void)setNickname:(NSString *)nickname;
    
    @end

    虽然在@property定义为readonly,但是随后提供了一个自定义的setter方法,那么实际作用等同于readwrite了。当尝试对其进行赋值,编译器将通过。

    【示例三】

    #import <Foundation/Foundation.h>
    
    @interface NewClass : NSObject
    
    @property (nonatomic,copy) NSString *nickname;
    
    //自定义getter方法
    - (NSString*)nickname;
    
    @end
    #import "NewClass.h"
    
    @implementation NewClass
    
    @synthesize nickname;
    
    - (NSString*)nickname
    {
        NSMutableString *str = [[NSMutableStringalloc] initWithString:nickname];
    
        [str appendString:@"_suffix"];
    
        return str;
    }
    
    @end
    - (void)viewDidLoad
    {
        [superviewDidLoad];
    
        NewClass *myClass = [[NewClass alloc] init];
    
        myClass.nickname = @"abc";
    
        NSLog(@"%@",myClass.nickname);
    
        //将输出abc_suffix
    }

    上面的例子里声明了一个属性nickname,默认为readwrite。但随后又提供了一个自定义的getter方法,并且在实现文件里将赋值后的值加上了一个后缀字符串。最终运行后,会发现自定义的getter方法生效了,它覆盖了@property自动生成的getter。

    通过上面的例子同时可以得到一个结论:假如声明了一个@property,名称为nickname。那么会生成一个名为nickname的成员变量、一个名为nickname的getter方法、一个setNickname的setter方法。

    再来看看atomic/nonatomic的区别。如果你用@synthesize去让编译器生成代码,那么atomic和nonatomic生成的代码是不一样的。如果使用atomic,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,仅仅靠atomic来保证线程安全是不够的。要写出线程安全的代码,还需要有同步和互斥机制。而nonatomic就没有类似的"线程安全"保证了。因此,很明显,nonatomic比atomic速度要快。这也是为什么,我们基本上所有用@property的地方,都用的是nonatomic了。

    最后着重了解一下assign、retain与copy:

    assign指定setter方法采用简单的赋值操作,而不更改索引计数。这也是没有明确指定setter语意的默认操作。基础数据类型(NSInteger、CGPoint)和C语言数据类型(int, float, double, char等)一般会采用assign。

    - (void)setNickname:(NSString *)strName
    {
        nickname = strName;
    }

    指定为retain后,先释放旧的(指针)对象,再设置为新的对象,并且索引计数加1。(指针拷贝)

    - (void)setNickname:(NSString *)strName
    {
        if(nickname!=strName)
        {
            [nickname release];
            nickname = [strName retain];
        }
    }

    指定为copy后,先释放旧的(指针)对象,再拷贝一份新的对象,不对(拷贝的)旧对象进行操作。(内容拷贝)

    - (void)setNickname:(NSString *)strName
    {
        if(nickname!=strName)
        {
            [nickname release];
            nickname = [strName copy];
        }
    }

    下面举例分析一下assign、retain和copy的一些不同之处:

    NSString *str = [[NSString alloc] initWithString:@"test"];

    这句话实际上产生了两个操作:

    1. 申请了一段内存用来存储"test",这里假设内存地址为0x1111
    2. 用一个名为str的指针,指向"test",假设指针的内存地址为0xaaaa

    先来看assign在这种情况下的示例:

    NSString *str2 = str;

    此时str2和str完全相同,地址都是0xaaaa,都指向地址为0x1111的内容"test"。所以assign操作相当于是一个"别名"的概念

    再来看retain的情况:

    NSString *str3 = [str retain];

    此时str3的地址不再是0xaaaa,可能是0xbbbb,但是仍然指向同一个对象(地址为0x1111,内容为"test")。retainCount(索引计数)增加1。

    最后看看copy的情况:

    NSString *str4 = [str copy];

    此时会新开辟一段内存来存放内容"test",假设地址为0x2222。同时会为指针str4也分配一段新内存(假设为0xcccc),然后将str4指向地址为0x2222的"test"。操作结束后,原来地址为0x1111的"test"以及地址为"0xaaaa"的指针均不受影响;地址为0xcccc的指针指向地址为0x2222的"test"。且retainCount为1。

    所以可以得出结论,retain是指针拷贝,copy是内容拷贝

    copy也分为深拷贝和浅拷贝,类似于C++

    (1) 深拷贝,就是新拷贝一块内存交给对象使用
    (2) 浅拷贝,就是觉得拷贝内存太浪费,直接给你我的地址吧,相当于retain

    在Objective-C里只有一种情况是浅拷贝,那就是不可变对象的copy,其它的都是深拷贝(包括不可变对象mutableCopy、可变对象的copy和mutableCopy)。

    适用场合:

    非对象的数据类型,比如int、float等基本数据类型用assign。

    对象数据类型,例如NSObject用retain。

    当类拥有mutable子类时,应该使用copy,而不是retain。例如:NSArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet、NSString。

    虽然规范上NSString做属性都是写成copy,其实在不存在NSMutableString赋值给NSString时,是可以采用retain来避免字符串拷贝带来的消耗的。

    MyClass.h
    #import <Foundation/Foundation.h>
    
    @interface NewClass : NSObject
    
    @property (nonatomic,retain) NSString *strRetain;
    @property (nonatomic,copy) NSString *strCopy;
    
    @end
    MyClass.m
    #import "NewClass.h"
    
    @implementation NewClass
    
    @synthesize strRetain;
    @synthesize strCopy;
    
    @end
    - (void)viewDidLoad
    {
        [superviewDidLoad];
    
        NSMutableString *str = [[NSMutableStringalloc] initWithCapacity:100];
    
        [str setString:@"dog"];
    
        NewClass *myClass = [[NewClass alloc] init];
    
        myClass.strRetain = str;
    
        myClass.strCopy = str;
    
        NSLog(@"retain string is %@",myClass.strRetain);
    
        NSLog(@"copy string is %@",myClass.strCopy);
    
        [str setString:@"cat"];
    
        NSLog(@"retain string is %@",myClass.strRetain);
    
        NSLog(@"copy string is %@",myClass.strCopy);
    }

    将依次输出"dog, dog, cat, dog"。这样就可以看出,当使用retain方式的时候,NSMutableString的内容变化时,语义上应该不可变的NSString也变化了,而用copy则是始终保持赋值时的内容。在实际开发中,如果此NSString不存在NSMutableString赋值的情况,就可以采用retain来代替copy。

    最后说一下iOS5加入ARC机制后新加入的一些关键词:

    strong -- 强引用,关键字为__strong,用该属性声明的变量将成为对象的持有者。(默认)

    weak -- 弱引用,关键字为__weak,用该属性声明的变量并没有对象的所有权,并且当对象失去持有者之后,变量会被自动设置为nil。

    unsafe_unretained,关键字为__unsafe__unretained,iOS5之前的版本用这个关键字来代替__weak,于_weak的区别在于是否执行nil赋值。如果所指向的对象被释放了,这个指针就是一个野指针了。

    autoreleasing,关键字为__autoreleasing,用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放。换个说法:该关键字使对象延迟释放。比如你想传一个未初始化的对象引用到一个方法当中,并在此方法中实例化该对象,那么这种情况可以使用__autoreleasing。它被经常用于函数有值参数返回时的处理,比如下面的例子:

    - (void)testSomething:(NSObject * __autoreleasing *)objParam
    {
        *objParam = [[NSObject alloc] init];
    }
    
    - (void)viewDidLoad
    {
        [superviewDidLoad];
    
        NSObject *obj = nil;
    
        [self testSomething:&obj];
    
        NSLog(@"%p",obj);
    }

    另外,下面的写法是等效的:

    id *obj == id __autoreleasing * obj;

    NSObject **obj == NSObject * __autoreleasing * obj;

    使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。

     

  • 相关阅读:
    项目开发中需要注意的
    数据库函数
    C#中 ?. 运算符
    字符串格式化String.Format
    day37 进程理论 多进程
    36 网络编程---操作系统 并发
    day35 socket的常用方法,
    day34
    day33天 网络编程udp pycharm控制台输出带颜色
    day32 网络编程初识
  • 原文地址:https://www.cnblogs.com/CoderWayne/p/2845404.html
Copyright © 2020-2023  润新知