Unit 1

来源:互联网 发布:数据统计专业 编辑:程序博客网 时间:2024/06/15 08:35

本系列文章郑重声明

笔试题绝大多数来自于互联网,其解答为原作者解答或者为他人所答以及笔者对相关问题的一些见解和看法,资料来源会在文章后面附上。如有不恰当之处,还望斧正!

什么时候用weak关键字,相比assign有什么不同?

使用场景

  • 为了解决ARC下可能出现的循环引用的问题,即对象A的内存释放依赖于对象B的内存释放,而对象B的内存释放又反过来依赖于对象B的内存释放,最终导致的结果是对象A、B内存泄漏。通常用于delegate内存管理方式的关键字声明以及Block中对self的弱引用,具体可用如下代码来表示:(虽然后者的代码表现形式不同,但作用机理是一致的)
@property (weak, nonatomic) id<XXX> delegate;/*只要你在block里用到了self所拥有的东西,不一定是显示地出现self,就会出现循环引用,它们的引用关系可表示为:self->XXblock->_XX(self.XX),亦即self的释放依赖于XXblock的释放,而XXblock的释放依赖于_XX(self.XX)的释放。为了解决这个问题就需要将一端的引用关系变为weak,很显然在这里将Block内部的self强引用是罪魁祸首,因此有了下面这段将Block内部self变成弱引用的代码。补:在Swift中,闭包的作用与OC中的block类似,而在闭包内要引用当前对象的属性,则必须加上self.来修饰属性,因为Swift中没有_属性这样一个成员变量。此时可以说循环引用必须要显式使用self。(2017-6-16)*/__weak typeof(self) weakSelf = self;[XXX ^(XXX) {        __strong typeof(weakSelf) strongSelf = weakSelf;        if (strongSelf) {           //在Block内部强引用是为了防止在weakSelf的使用过程中被释放掉        }}];
  • 使用IBOutlet引用IB里的某一控件时。因为控件是被某一具体的storyboard文件强引用的,storyboard对它有生杀予夺的权利,对外界的输出口只是一个引用关系,它的所属关系是确认的。这就好比你写论文时,引用别人文章,你只能决定它是以什么形式出现,而无权干预它原本的客观存在状态。

不同点

weak表征的是一种非拥有关系(nonowning relationship),被它修饰的属性不能决定原对象的生命周期。当原对象被回收时,此属性也将被清空,它只能修饰objc对象。assign是一种纯量类型(scalar type),只进行简单的赋值操作,同时它还可以作用于非objc对象。


于2017-6-24补:weak(__weak)本质上是靠观察者模式(KVO)来实现的,(通过在一个全局的哈希表中找出该对象的弱引用)一旦对象被释放,指向它的__weak指针就会被置为nil。而assign__unsafe_unretainedassign类型的引用)强调的是简单的赋值,并且不会增加对象的引用计数(所以基本类型使用assign修饰没有任何问题)。但此时即使对象被释放,对应指针依旧会指向其内存起始地址。所以当你向被其修饰的对象发送消息时,就一定要保证对象是存在的,否则就会出现野指针错误,这就是它起名为不安全不保持的原因。在MRC时代,就是以assgin来实现弱引用的。对于高级iOS开发工程师来说,__unsafe_unretained类型指针是自实现内存管理的利器,YYModel就是一个很典型的框架,所以它的效率比其他解析JSON数据的框架要高一些,因为这是基于使用weak关键字效率略低的一个事实。

怎么用copy关键字?

通常情况下,它出现在NSStringNSArrayNSDictionary的属性声明或这些类型对象的复制过程中。对于对象的复制有不可变拷贝(copy)和可变拷贝(mutableCopy),如果你想要调用这两个方法来拷贝某类的对象,那么该类必须遵守并实现NSCopyingNSMutableCopying,对应的方法是copyWithZone:和mutableCopyWithZone:,下面这张图(来源)是关于它们的总结:

这里写图片描述

总结一句话:不可变类型对象可变复制产生新对象,不可变复制只是指针复制(浅复制);可变类型对象的复制一定产生新的对象,且都是内容复制(深复制),不可变复制产生的新对象不可变。

现在来试着理解这样的一个设计:不可变对象的不可变赋值的结果本身也是不可变的,而原对象本身也是具有这样的特征,所以为了提高效率,使用浅复制是很好的决策;而可变复制改变了对象的本质,即不可变->可变,当然这得生成新的对象,并使用深复制来保证两者目前的内容一致。同理,可变对象的不可变复制也是如此。而可变对象的可变复制如果采用浅复制的话,那么复制后的对象与原对象会同步变化,所以为了解决这个问题,使用内容拷贝也是理所应当的。(记于2017-7-18)

使用场景对应一种“表里不一”的行为,即给不可变类型直接用赋值可变类型对象。由于多态性,这种行为的许可可能会导致你想关注某个时刻的数据对象而不关心之后数据的变动的期望落空,发生这种情况的原因是可变对象的可能的持续变化对表面不可变对象暗地里的不断修改,可用以下代码表示:

//......@property (strong, nonatomic) NSArray *books;//......NSMutableArray *currentBooks = [NSMutableArray arrayWithObjects:@"book1", @"book2", nil];self.books = currentBooks;[currentBooks addObject:@"book3"];NSLog(@"%@", self.books);//print the result://(book1, book2, book3)//将books属性strong修饰符换成copy或将currentBooks复制之后再赋值给self.books,对currentBooks的修改不会影响self.books,打印结果即为://(book1, book2)

为什么使用nonatomic?

首先需要说明的是在iOS平台上由于对性能的考量,在声明属性时几乎都会用到nonatomic关键词,而在macOS上则没有这一个性能的限制,这也就导致了系统默认的就是atomic,也就让我们做iOS开发的多做了一些活。

atomicnonatomic的区别在于对其修饰的属性所生成的gettersetter不同,如用前者声明,是这样存取方法:

@property (copy) NSMutableArray *books;- (NSMutableArray *)books {    NSMutableArray *books = nil;    @synchronized(self) {        books = _books;    }    return books;}- (void)setBooks:(NSMutableArray *)books {    @synchronized(self) {        _books = books;    }}

那么使用atomic的目的就昭然若揭了,它试图通过线程同步(synchronized)来确保线程安全,即多线程竞态时保证数据的完整性,通俗来说就是一个线程在连续多次操作某属性值的过程中同时又有别的线程在访问该值,如果不加保护,就会破坏数据的一致性(Consistency)。举个例子,你银行卡里有1000块钱,你和你女朋友都知道密码。某天你到ATM想要取500元,于此同时,你女朋友买了1000元的化妆品正在用支付宝付款。你的操作尚未完成,表明银行卡中余额依旧是1000元,你女朋友在此时就能够提交付款请求。你和你女朋友取/付款完成后,银行卡里的余额就变成了-500元,这显然是不合理的。(虽然我们想这样!)这就是多线程竞争导致的数据一致性被破坏,是线程不安全的。使用线程同步则是表示当发生多线程竞争时,一个线程所有操作完成时才能执行另一个线程的任务,这与数据库中事务的并发控制有相同的意义。对应此例就是在你取款之后,修改余额成功后,你女朋友才能完成最终付款,显然她在经过一定时间的等待之后是以失败告终的:余额不足。

然而这个关键词却并不能保证线程的安全,因为实际的多线程访问要复杂得多得多,不然为什么我们还要GCD,而且它访问效率很差。因此我们宁愿使用nonatomic而不是atomic,涉及到多线程安全问题时,运用GCD才能很好地解决实际问题。

@property本质是什么?

@property = ivar + getter + setter

属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)

使用@property声明某一属性,实际上从编译器层面讲就是对应属性的存取方法,只不过默认情况还会生成“_属性名”的一个实例变量,这些都是在你使用这一关键字后在编译过程中自动添加的。

但是当你手动实现了gettersetter之后,编译器就不会再生成对应的存取方法了,同时也不会有“_属性名”这样的一个实例变量存在。这时,你需要用如下方式来手动添加:

@synthesize 属性名 = _属性名; //如果你不给属性名赋值的话,系统将默认生成与属性同名的成员变量,如果“_属性名”这个成员变量已经被你定义过了,系统就将不会为这个属性生成成员变量了

下面引用一段来阐述关于属性运行时的具体特征:(来源)

propertyruntime中是objc_property_t定义如下:

typedef struct objc_property *objc_property_t;

objc_property是一个结构体,包括nameattributes,定义如下:

struct property_t {    const char *name;    const char *attributes;};

attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下:

/// Defines a property attributetypedef struct {    const char *name;           /**< The name of the attribute */    const char *value;          /**< The value of the attribute (usually empty) */} objc_property_attribute_t;

attributes的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。

例如:我们定义一个string的property@property (nonatomic, copy) NSString *string;,通过 property_getAttributes(string)获取到attributes并打印出来之后的结果为T@”NSString”,C,N,V_string

其中T就代表类型,可参阅Type Encodings,C就代表Copy,N代表nonatomic,V就代表对于的实例变量。

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 gettersetter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。

属性的实现大致包含了一下几点:

  • 该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远
  • setter 与 getter 方法对应的实现函数
  • ivar_list :成员变量列表 (以下几种列表均属于该类)
  • method_list :方法列表
  • prop_list :属性列表

也就是说我们每次在增加一个属性,系统都会在 ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

@protocol 和 category 中如何使用 @property?

由上一个tip可知,@property实质上代表了属性的存取方法,如果在协议或者分类中添加了属性其实就是将属性的存取方法作为协议方法或者分类方法,你会看到编译器要求你实现相应的settergetter。至于生成实例变量的任务,你就需要用以下两个运行时函数在程序运行时动态添加:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)id objc_getAssociatedObject(id object, const void *key)//a sample demo- (void)setCallBackDelegate:(id<RefreshingCallBack>)callBackDelegate {    objc_setAssociatedObject(self, ((__bridge void *)@"callBackDelegate"), callBackDelegate, OBJC_ASSOCIATION_ASSIGN);}- (id<RefreshingCallBack>)callBackDelegate {    return objc_getAssociatedObject(self, ((__bridge void *)@"callBackDelegate"));}

另一种方式是:.m文件中定义一个私有的成员变量,当调用setter时对这个变量赋值,当调用getter时则返回这个变量。这么做的话,对象本身就并没有直接绑定这个属性了,简化了实现过程,同时变相地完成了属性与对象之间的联系。(记于2017-7-18)

参考并整理自:

sunnyxx: 招聘一个靠谱的iOS
@微博程序犭袁:iOSInterviewQuestions

0 0
原创粉丝点击