面試題學(xué)習(xí)(1)

前言

過幾天準(zhǔn)備面試了,搜了搜晚上的面試題,好多好多。說實(shí)話我大部分都答不上來,也沒法全看,找了《招聘一個(gè)靠譜的iOS》這套面試題。里面有原作者的鏈接,答案當(dāng)然已經(jīng)有了,但是每道題還是需要認(rèn)真想想,畢竟最終目的不是為了應(yīng)付面試,是為了自己的技術(shù)能更近一步,萬丈高樓平地起,一步一步來吧。
準(zhǔn)備一天弄8-10道題,也就是五天內(nèi)全都弄完,最近也沒什么項(xiàng)目,白天可以用來搞這個(gè),時(shí)間充足,所有題目能用代碼驗(yàn)證的都會(huì)用代碼驗(yàn)證并附上結(jié)果保證結(jié)論的正確性。

一、風(fēng)格糾錯(cuò)題

codeStyle1.png

下面是一種答案

typedef NS_ENUM(NSInteger, CYLSex) {
   CYLSexMan,
   CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

從枚舉開始分析

typedef NS_ENUM(NSInteger,YFQSex) {
    YFQSexMan,
    YFQSexWoman
};

這是作者給出的,圖片中的枚舉方式是蘋果并不推薦的。蘋果推薦使用NS_OPTIONS與NS_ENUM。
那個(gè)NS_OPTIONS與NS_ENUM又有什么區(qū)別呢。
我們先來看兩者例子

typedef NS_ENUM(NSInteger,YFQSex) {
    YFQSexMan = 0,
    YFQSexWoman = 1
};
typedef NS_OPTIONS(NSUInteger, YFQCharacter) {
    YFQCharacterIrritability = 1<<0,
    YFQCharacterBrave = 1<<1,
    YFQCharacterLazy = 1<<2
};
  1. 先說使用場(chǎng)景,NS_ENUM用在只能存在一個(gè)枚舉值,例如Switch case。NS_OPTIONS用在允許同時(shí)存在多個(gè)枚舉值。NS_ENUM定義通用枚舉,NS_OPTIONS定義位移枚舉。
  2. 那么問題來了,位移枚舉又是什么。那什么又是位移呢,在這里(<<)代表左移。先來看<<的時(shí)候發(fā)生了什么。
以1<<2為例
移位發(fā)生在二進(jìn)制下,所以翻譯成我們的語(yǔ)言就是將0000 0001左移兩位,變成了0000 0100。
轉(zhuǎn)化為十進(jìn)制就是2的2次方4,結(jié)果會(huì)在下方驗(yàn)證。

那么我們知道了移位,返回來再看NS_OPTIONS的使用場(chǎng)景,為什么通過移位就能實(shí)現(xiàn)多個(gè)枚舉值同時(shí)存在呢。

    self.character = YFQCharacterLazy | YFQCharacterBrave;
    if(self.character & YFQCharacterLazy){
        NSLog(@"lazy");
    }
    if(self.character & YFQCharacterBrave){
        NSLog(@"brave");
    }
    if(self.character & YFQCharacterIrritability){
        NSLog(@"irritability");
    }
    NSLog(@"%lu",YFQCharacterLazy);

這里模擬了同時(shí)具備lazy和brave性格,打印如下。

2018-04-10 13:52:37.652530+0800 YFQResponseChain[24326:659897] lazy
2018-04-10 13:52:37.652799+0800 YFQResponseChain[24326:659897] brave
2018-04-10 13:52:37.652959+0800 YFQResponseChain[24326:659897] 4

lazy二進(jìn)制0000 0100,brave二進(jìn)制0000 0010,或運(yùn)算后0000 0110,所以與運(yùn)算就會(huì)打印出lazy和brave。lazy打印出來也是4,證明了上述的說法。
那么扯個(gè)淡

YFQCharacterHappy = 3<<2

打印出來是多少呢,經(jīng)過運(yùn)算應(yīng)該是12。

  1. 那么最后一個(gè)問題,為什么會(huì)出現(xiàn)兩種枚舉呢。
    在使用或運(yùn)算操作兩個(gè)枚舉值時(shí),C++默認(rèn)為運(yùn)算結(jié)果的數(shù)據(jù)類型是枚舉的底層數(shù)據(jù)類型即NSUInteger,且C++不允許它隱式轉(zhuǎn)換為枚舉類型本身,所以C++模式下定義了NS_OPTIONS宏以保證不出現(xiàn)類型轉(zhuǎn)換。(摘自以下文章,說實(shí)話個(gè)人不是太理解)。
    該部分參考以下兩文章
    詳解枚舉NS_OPTIONS與NS_ENUM的區(qū)別與格式
    位運(yùn)算和枚舉

類的命名

@interface CYLUser : NSObject<NSCopying>

如果工程項(xiàng)目非常龐大,需要拆分成不同的模塊,可以在類、typedef宏命名的時(shí)候使用前綴。
前綴為三個(gè)字母,雙字母前綴為 Apple 的類預(yù)留。
還要注意使用駝峰命名法。
同時(shí)也解釋了枚舉用YFQSex。
此處的NSCopying會(huì)在接下來的問題中詳細(xì)解答。

屬性

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;

由于能力原因,這里我也不會(huì)寫太深,盡量挨個(gè)都說到。
首先,屬性定義的規(guī)則是 原子性,讀寫 和 內(nèi)存管理。

  1. @property聲明可以直接生成一個(gè)set和get方法,寫和讀。
    如果括號(hào)內(nèi)的關(guān)鍵字一個(gè)不寫,
    readwrite,atomic(即不寫nonatomic)
    非ARC環(huán)境下, 默認(rèn)為assign
    在ARC環(huán)境下,默認(rèn)為strong
  2. nonatomic與atomic
    atomic保證多線程安全,性能低于nonatomic。
    比如對(duì)于atomic,當(dāng)線程a的get方法還沒結(jié)束,線程b調(diào)用set方法,那么線程a還是能得到一個(gè)完整的對(duì)象。但是這樣消耗資源。
    對(duì)于nonatomic就不保證線程a能得到完整的對(duì)象,因此速度快。
    但是atomic也不能保證線程的絕對(duì)安全,只是增加了安全的幾率。
  3. readonly
    這個(gè)比較簡(jiǎn)單,如果我們聲明了這個(gè)關(guān)鍵字,那么對(duì)應(yīng)的屬性就無法修改,我們?cè)谑褂胹et時(shí)會(huì)報(bào)錯(cuò),這個(gè)關(guān)鍵字的聲明根據(jù)使用場(chǎng)景來定。
    既然該類中已經(jīng)有一個(gè)“初始化方法” (initializer),用于設(shè)置“姓名”(Name)、“年齡”(Age)和“性別”(Sex)的初始值: 那么在設(shè)計(jì)對(duì)應(yīng) @property 時(shí)就應(yīng)該盡量使用不可變的對(duì)象:其三個(gè)屬性都應(yīng)該設(shè)為“只讀”。用初始化方法設(shè)置好屬性值之后,就不能再改變了。在本例中,仍需聲明屬性的“內(nèi)存管理語(yǔ)義”。于是可以把屬性的定義改成這樣
  4. copy
    這個(gè)在下面有個(gè)問題中專門解釋
  5. assign
    先說使用場(chǎng)景,assign使用在基本的數(shù)據(jù)類型(NSUInteger等)、枚舉上。
    這里說一下為什么不用在其他數(shù)據(jù)類型上。assign做的事是讓這個(gè)屬性只復(fù)制你給它的值得指針,所以他們指向共同的區(qū)域。當(dāng)使用malloc分配一塊內(nèi)存區(qū)域,并將地址賦值給指針a。指針b也想指向這塊區(qū)域,就將a賦值給了b,此處用assign。當(dāng)a不需要這塊區(qū)域時(shí),可以釋放掉。此時(shí)b就變成了野指針,程序會(huì)crash掉。
    代碼如下圖
@property (nonatomic,weak) id      weakPoint;

@property (nonatomic,assign) id    assignPoint;

@property (nonatomic,strong) id    strongPoint;
 self.strongPoint = [NSDate date];
 NSLog(@"strong屬性:%@",self.strongPoint);
 self.weakPoint = self.strongPoint;
 self.assignPoint = self.strongPoint;
 self.strongPoint = nil;
 NSLog(@"weak屬性:%@",self.weakPoint);
 NSLog(@"assign屬性:%@",self.assignPoint);
2018-04-10 15:34:15.972369+0800 YFQResponseChain[26616:721600] strong屬性:2018-04-10 07:34:15 +0000
2018-04-10 15:34:15.972608+0800 YFQResponseChain[26616:721600] weak屬性:(null)

會(huì)在最后crash。
代碼中涉及了weak,可以看到weak并沒有crash而是變成了null。這里也是weak和assign的區(qū)別。weak相較于assign多了一個(gè)功能就是當(dāng)它指向的對(duì)象不存在時(shí),會(huì)自動(dòng)賦值nil,再向weak修飾的屬性發(fā)消息時(shí)也不會(huì)crash。
摘自文章
再戰(zhàn) OC @property屬性
## IOS開發(fā)中ARC下的assign和weak區(qū)別

  1. 屬性類型
    這里講int改為NSUInteger。
    oc的建議使用類型
    int -> NSInteger
    unsigned -> NSUInteger
    float -> CGFloat
    動(dòng)畫時(shí)間 -> NSTimeInterval
    使用NSUInteger是保證在64位系統(tǒng)和32位系統(tǒng)下的兼容性,int只能運(yùn)行在32位系統(tǒng)下。

方法

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

這里涉及到了方法的命名規(guī)則,首先參數(shù)可以不加with或者使用with...and。直接寫參數(shù)名簡(jiǎn)潔明了,不建議使用and。
至于快捷構(gòu)造方法作者是這么解釋的,我有點(diǎn)不太懂。
如果設(shè)計(jì)了“初始化方法” (initializer),也應(yīng)當(dāng)搭配一個(gè)快捷構(gòu)造方法。而快捷構(gòu)造方法的返回值,建議為 instancetype,為保持一致性,init 方法和快捷構(gòu)造方法的返回類型最好都用 instancetype。
至于為什么把dologin刪除,和MVC設(shè)計(jì)模式有關(guān)。

二、什么情況使用 weak 關(guān)鍵字,相比 assign 有什么不同?

第一個(gè)問題

1、在arc模式下,針對(duì)可能出現(xiàn)的循環(huán)引用的情況,在其中一端使用weak,例如delegate,block
2、自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒有必要再?gòu)?qiáng)引用一次,此時(shí)也會(huì)使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當(dāng)然,也可以使用strong。
那么循環(huán)引用是什么呢,簡(jiǎn)單來說就是a強(qiáng)引用b,b強(qiáng)引用a。當(dāng)我們想釋放掉a的時(shí)候,發(fā)現(xiàn)a被b強(qiáng)引用了,于是我們就得先釋放掉b,但是b又被a強(qiáng)引用,這就造成了ab都無法釋放。真實(shí)的情況可能會(huì)被這個(gè)復(fù)雜,比如block。a有一個(gè)屬性teacher,a就是強(qiáng)引用teachter,teacher又強(qiáng)引用了一個(gè)block,block中又調(diào)用了a中的一個(gè)屬性于是就造成了a->teacher->block->a;解決辦法就是在teacher引用block之前使用weak修飾self,使block中對(duì)self的應(yīng)用是弱引用。delegate的使用場(chǎng)景也同樣,是父子關(guān)系。
摘自以下文章:
理解 ARC 下的循環(huán)引用
ARC下用塊(block)的循環(huán)引用問題樣例探究
控件使用weak的會(huì)在下面的會(huì)問題中提到。

三、怎么用 copy 關(guān)鍵字?

這個(gè)問題問的是怎么使用,關(guān)于為什么使用以及和其他關(guān)鍵字的區(qū)別會(huì)在下面的問題中討論。

  1. 任何可以用一個(gè)可變的對(duì)象設(shè)置的((比如 NSString,NSArray,NSURLRequest))屬性的內(nèi)存管理類型必須是 copy 的。
    這是為了確保防止在不明確的情況下修改被封裝好的對(duì)象的值。比如執(zhí)行 array(定義為 copy 的 NSArray 實(shí)例) = mutableArray,copy 屬性會(huì)讓 array 的 setter 方法為 array = [mutableArray copy], [mutableArray copy] 返回的是不可變的 NSArray 實(shí)例,就保證了正確性。用其他屬性修飾符修飾,容易在直接賦值的時(shí)候,array 指向的是 NSMuatbleArray 的實(shí)例,在之后可以隨意改變它的值,就容易出錯(cuò)。
  2. 屬性可以存儲(chǔ)一個(gè)代碼塊。為了讓它存活到定義的塊的結(jié)束,必須使用 copy (block 最早在棧里面創(chuàng)建,使用 copy讓 block 拷貝到堆里面去)
    (關(guān)于block中為什么要使用copy在原答案中會(huì)有更詳細(xì)的解釋,我復(fù)制過來也沒什么意思,自己去體會(huì)尋找的樂趣吧)
    擴(kuò)展文章
    iOS開發(fā)怎么使用copy關(guān)鍵字

四、這個(gè)寫法會(huì)出什么問題: @property (copy) NSMutableArray *array;

首先使用copy錯(cuò)誤,如上個(gè)問題所說,copy修飾的屬性在調(diào)用set方法時(shí),會(huì)生成一個(gè)不可變對(duì)象,所以實(shí)際上如果調(diào)用這個(gè)屬性會(huì)生成一個(gè)NSArray。如果我們按照NSMutableArray來使用屬性就會(huì)報(bào)錯(cuò),代碼如下

NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
    self.array = array;
    [self.array removeObjectAtIndex:0];
'-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x60000023c900'

還有就是此處沒有聲明原子性,默認(rèn)是atomic,在ios中這會(huì)很影響性能,除非特殊需要,否則都使用nonatomic。
在Mac OS X中應(yīng)該是會(huì)使用到atomic中的,原子性應(yīng)該用在多線程中,保證運(yùn)算的完整性。

五、如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關(guān)鍵字的 setter?

這個(gè)重點(diǎn)講講copy吧
關(guān)于第一個(gè)問題更通俗的說法是自定義對(duì)象支持拷貝操作。我們先來看一個(gè)具體的使用場(chǎng)景。

UserModel *model = [[UserModel alloc]initWithName:@"tony" Age:23];
UserModel *person1 = [model copy];
    person1.name = @"rose";
    NSLog(@"person.name = %@,person.age = %lu",model.name,model.age);
    NSLog(@"person1.name = %@,person1.age = %lu",person1.name,model.age);
2018-04-11 11:25:12.426325+0800 YFQResponseChain[35246:1390059] person.name = tony,person.age = 23
2018-04-11 11:25:12.426470+0800 YFQResponseChain[35246:1390059] person1.name = rose,person1.age = 23

這里我們將name的關(guān)鍵詞改為readWrite,所以我們看到新copy的person1對(duì)象的name是rose,age則是copy過來的23。
那么怎么實(shí)現(xiàn)的copy呢,首先在第一個(gè)問題中我們針對(duì)usermodel類聲明了協(xié)議NSCopying。然后我們?cè)?m文件里加了這樣一個(gè)方法。

- (id)copyWithZone:(NSZone *)zone{
    UserModel *copy = [[[self class] allocWithZone:zone] initWithName:_name Age:_age];
    return copy;
}

接下來的討論將會(huì)圍繞這個(gè)方法展開。
一下內(nèi)容摘自《Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》第22條。
為什么會(huì)出現(xiàn)NSZone呢?因?yàn)橐郧伴_發(fā)程序時(shí),會(huì)據(jù)此把內(nèi)存鳳城不同的區(qū)(zone),而對(duì)象會(huì)創(chuàng)建在某個(gè)區(qū)里?,F(xiàn)在不用了,每個(gè)程序只有一個(gè)區(qū):“默認(rèn)區(qū)”(dafault zone)。所以說,盡管必須實(shí)現(xiàn)這個(gè)方法,但你不必?fù)?dān)心其中的zone參數(shù)。
copy方法由NSObejct實(shí)現(xiàn),該方法只是以默認(rèn)區(qū)為參數(shù)來調(diào)用“copyWithZone”。我們總是想覆寫copy方法,其實(shí)真正需要實(shí)現(xiàn)的卻是“copyWithZone”方法,這個(gè)問題大家注意。

#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject<NSCopying>

@property (nonatomic, readonly, copy) NSString *firstName;
@property (nonatomic, readonly, copy) NSString *lastName;

- (instancetype)initWithFirstName:(NSString *)firstName LastName:(NSString *)lastName;


@end
#import "EOCPerson.h"

@implementation EOCPerson

- (id)copyWithZone:(NSZone *)zone
{
    EOCPerson *copy = [[[self class] allocWithZone:zone]
                       initWithFirstName:_firstName LastName:_lastName];
    return copy;
}
- (instancetype) initWithFirstName:(NSString *)firstName LastName:(NSString *)lastName{
    if(self = [super init]){
        _firstName = [firstName copy];
        _lastName = [lastName copy];
    }
    return self;
}

@end

若想使某個(gè)類支持拷貝功能,只需聲明該類遵從NSCopying協(xié)議,并實(shí)現(xiàn)其中法即可,也就是上文提到的方法和之前的協(xié)議聲明。在上述例子中實(shí)現(xiàn)的“copyWithZone:”中,我們直接把待拷貝的對(duì)象交給“全能初始化方法”,令其執(zhí)行所有初始化工作。然而有的時(shí)候,除了要拷貝對(duì)象,還要完成其他一些操作,比如類對(duì)象中的數(shù)據(jù)結(jié)構(gòu)并未在初始化方法中設(shè)置好,需要另行設(shè)置。舉個(gè)例子,

#import "EOCPerson.h"

@implementation EOCPerson{
    NSMutableSet *_friends;
}

- (instancetype) initWithFirstName:(NSString *)firstName LastName:(NSString *)lastName{
    if(self = [super init]){
        _firstName = [firstName copy];
        _lastName = [lastName copy];
        _friends = [NSMutableSet new];
    }
    return self;
}

- (void)addFriend:(EOCPerson *)person{
    [_friends addObject:person];
}

- (void)removeFriend:(EOCPerson *)person{
    [_friends removeObject:person];
}

- (id)copyWithZone:(NSZone *)zone
{
    EOCPerson *copy = [[[self class] allocWithZone:zone]
                       initWithFirstName:_firstName LastName:_lastName];
    copy->_friends = [_friends mutableCopy];
    return copy;
}

@end

這次所實(shí)現(xiàn)的方法比原來多了一些代碼,它把對(duì)象的_friends實(shí)例變量復(fù)制了一份,令copy對(duì)象的_friends實(shí)例變量指向這個(gè)復(fù)制過的set。注意,這里使用了->語(yǔ)法,因?yàn)開friends并非屬性,只是個(gè)在內(nèi)部使用的實(shí)例變量。其實(shí)也可以聲明一個(gè)屬性來表示它,不過由于該變量不會(huì)在本類之外使用,所以那么做沒必要。
這個(gè)例子提出了一個(gè)有趣的問題:為什么要拷貝_friends實(shí)例變量呢?不拷貝這個(gè)變量,直接令兩個(gè)對(duì)象共享同一個(gè)可變的set是否更簡(jiǎn)單些呢?如果真的這樣做了,那么在給原來的對(duì)象添加一個(gè)新朋友后,靠背過的那個(gè)對(duì)象居然也“神奇地”與之為友了。在本例中,這顯然不是我們想要的效果。然而,那個(gè)set若是不可變的,則無需復(fù)制,因?yàn)槠渲械膬?nèi)容畢竟不會(huì)改變,所以不用擔(dān)心此類問題。如果復(fù)制了,那么內(nèi)存中將會(huì)有兩個(gè)一摸一樣的set,反而造成浪費(fèi)。
通常情況下,應(yīng)該像本例這樣,采用全能初始化方法來初始化待拷貝的對(duì)象。不過有些時(shí)候不能這么做,因?yàn)槿艹跏蓟椒〞?huì)產(chǎn)生一些“副作用”,這些附加操作對(duì)目前的要拷貝的對(duì)象無益。比如,初始化方法可能要設(shè)置一個(gè)復(fù)雜的內(nèi)部數(shù)據(jù)結(jié)構(gòu),可是在拷貝后的對(duì)象中,這個(gè)數(shù)據(jù)結(jié)構(gòu)立刻就要用其他蘇劇來覆寫,所以沒必要再設(shè)置一遍。
仔細(xì)看看剛才那個(gè)“copyWithZone:”方法,你就會(huì)發(fā)現(xiàn),存放朋友對(duì)象的那個(gè)set通過mutablecopy方法來復(fù)制的。此方法來自另個(gè)叫做NSMutableCopying的協(xié)議。該協(xié)議與NSCopying類似,也只定義了一個(gè)方法,然而方法名不同:

- (id)mutableCopyWithZone:(NSZone *)zone

mutableCopy這個(gè)“輔助方法”與copy類似,也是用默認(rèn)的zone參數(shù)來調(diào)用“mutableCopyWithZone”。如果你的類氛圍可變版本與不可變版本,那么就應(yīng)該實(shí)現(xiàn)NSMutableCopying。若采用此模式,則在可變類中覆寫“copyWithZone:”方法時(shí),不要反悔可變版本,而應(yīng)返回一份不可變版本。無論當(dāng)前實(shí)例是否可變,若需獲取其可變版本的拷貝,均應(yīng)使用mutableCopy方法。同理,若需要不可變的拷貝,則總應(yīng)通過copy方法來獲取。
對(duì)于不可變的NSArry與可變的NSMutableArray來說,下列關(guān)系總是成立的:
-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
有個(gè)微妙的情況要注意:在可變對(duì)象上調(diào)用copy方法會(huì)返回令惡意不可變的類的實(shí)例。這樣做是為了能在可變版本與不可變版本之間自由切換。要實(shí)現(xiàn)此目標(biāo),還有個(gè)辦法,就是提供三個(gè)方法:copy、immutableCopy、mutableCopy,其中,copy所返回的拷貝對(duì)象與當(dāng)前對(duì)象的類型一致,而另外兩個(gè)方法則返回不可變版本和可變版本的拷貝。但是,如果調(diào)用者并不知道其所用的實(shí)例是否真的可變,那么這種做法就不太好。某種方法可能會(huì)把NSMutableArray對(duì)象當(dāng)作NSArray返回給你,而你在上邊調(diào)用copy方法來復(fù)制他。此時(shí)你以為拷貝后的對(duì)象應(yīng)該是不可變數(shù)組,但實(shí)際上它確實(shí)可變的。
可以查詢類型信息譯判斷待拷貝的實(shí)例是否可變,不過那樣做比較麻煩,因?yàn)槊看螐?fù)制對(duì)象的時(shí)候都得查詢。為了安全起見,最后還是會(huì)使用immutableCopy和mutableCopy這兩個(gè)方法來復(fù)制對(duì)象,而這么做就和只有copy與mutableCopy方法的設(shè)計(jì)方案毫無二致。把拷貝方法稱為copy而非immutableCopy的原因在于,NSCopying不僅設(shè)計(jì)給那些具有可變版本和不可變版本的類來用,而且還要供其他一些類使用,而那些類沒有可變和不可變之分,所以說,把拷貝方法叫做immutableCopy不合適。
在編寫拷貝方法時(shí),還要決定一個(gè)問題,就是應(yīng)該執(zhí)行“深拷貝”還是“淺拷貝”。深拷貝的意思就是:在拷貝對(duì)象自身時(shí),將其底層數(shù)據(jù)也一并復(fù)制過去。Foundation框架中所有Collection類在默認(rèn)情況下都執(zhí)行淺拷貝,也就是說,只拷貝容器對(duì)象本身,而不復(fù)制其中數(shù)據(jù)。這樣做的主要原因在,容器內(nèi)的對(duì)象未必都能拷貝,而且調(diào)用者也未必想要在拷貝容器時(shí)一并拷貝其中的每個(gè)對(duì)象。一般情況下,我們會(huì)遵照系統(tǒng)框架使用的模式,在自定義的類中以淺拷貝的方式實(shí)行“copyWihtZone:”方法。但如果有必要的話,也可以增加一個(gè)執(zhí)行深拷貝的方法。以NSSSet威力,該類提供了下面這個(gè)初始化方法,用以執(zhí)行深拷貝:

- (id)initWithSet:(NSArray *)array copyItemsL:(BOOL)copyItems

若copyItem參數(shù)舍為YES,則該方法會(huì)向數(shù)組中的每個(gè)元素發(fā)送copy消息,用拷貝好的元素創(chuàng)建新的set,并將其返回給調(diào)用者。
以下面為例:

- (id)deepCopy{
    EOCPerson *copy = [[[self class]alloc]initWithFirstName:_firstName LastName:_lastName];
    copy->_friends = [[NSMutableSet alloc]initWithSet:_friends copyItems:YES];
    return copy
}

以下文章中有一些總結(jié)可以:
總結(jié)
下面是我自己簡(jiǎn)單的總結(jié):

WechatIMG38.jpeg

關(guān)于第二個(gè)問題,原作者的意思是沒必要重寫copy的setter方法。
這個(gè)我先也不寫了,等有時(shí)間再找找資料。

六、@property 的本質(zhì)是什么?ivar、getter、setter 是如何生成并添加到這個(gè)類中的

  1. @proerty的本質(zhì)在之前寫的一篇文章里有提到
    這篇文章
  2. 自動(dòng)合成
    完成屬性定義后,編譯器會(huì)自動(dòng)編寫訪問這些屬性所需的方法,此過程叫做“自動(dòng)合成”(autosynthesis)。需要強(qiáng)調(diào)的是,這個(gè)過程由編譯 器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter、setter 之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字。在前例中,會(huì)生成兩個(gè)實(shí)例變量,其名稱分別為 _firstName 與 _lastName。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語(yǔ)法來指定實(shí)例變量的名字.
@synthesize name = _myname;

以下引用作者原話,我看不太懂
我為了搞清屬性是怎么實(shí)現(xiàn)的,曾經(jīng)反編譯過相關(guān)的代碼,他大致生成了五個(gè)東西

OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset),這個(gè)偏移量是“硬編碼” (hardcode),表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。
setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)
ivar_list :成員變量列表
method_list :方法列表
prop_list :屬性列表
也就是說我們每次在增加一個(gè)屬性,系統(tǒng)都會(huì)在 ivar_list 中添加一個(gè)成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個(gè)屬性的描述,然后計(jì)算該屬性在對(duì)象中的偏移量,然后給出 setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn),在 setter 方法中從偏移量的位置開始賦值,在 getter 方法中從偏移量開始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對(duì)象偏移量的指針類型進(jìn)行了類型強(qiáng)轉(zhuǎn).

七、@protocol 和 category 中如何使用 @property

  1. protocol
    EOCPerson.h
@protocol personDelegate <NSObject>

@property (nonatomic, copy) NSString *job;

@end


@interface EOCPerson : NSObject<NSCopying>

UserModel.h

@interface UserModel : NSObject<NSCopying,personDelegate>{
    NSString *_job;
}

UserModel.m
自動(dòng)

@synthesize job;

手動(dòng)

- (void)setJob:(NSString *)job{
    _job = job;
}
- (NSString *)job{
    return _job;
}

運(yùn)行

    person1.job = @"伐木";
    NSLog(@"job = %@",person1.job);

結(jié)果

2018-04-11 16:11:07.771528+0800 YFQResponseChain[38677:1565889] job = 伐木

2.category的使用在以下文章
這篇文章

參考文章
iOS Category 和 Protocol 中的 Property 你們真的會(huì)了么?

八、runtime 如何實(shí)現(xiàn) weak 屬性

weak不論是用作property修飾符還是用來修飾一個(gè)變量的聲明其作用是一樣的,就是不增加新對(duì)象的引用計(jì)數(shù),被釋放時(shí)也不會(huì)減少新對(duì)象的引用計(jì)數(shù),同時(shí)在新對(duì)象被銷毀時(shí),weak修飾的屬性或變量均會(huì)被設(shè)置為nil,這樣可以防止野指針錯(cuò)誤,本文要講解的也正是這個(gè)特性,runtime如何將weak修飾的變量的對(duì)象在銷毀時(shí)自動(dòng)置為nil。

那么runtime是如何實(shí)現(xiàn)在weak修飾的變量的對(duì)象在被銷毀時(shí)自動(dòng)置為nil的呢?一個(gè)普遍的解釋是:runtime對(duì)注冊(cè)的類會(huì)進(jìn)行布局,對(duì)于weak修飾的對(duì)象會(huì)放入一個(gè)hash表中。用weak指向的對(duì)象內(nèi)存地址作為key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc,假如weak指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵在這個(gè)weak表中搜索,找到所有以a為鍵的weak對(duì)象,從而設(shè)置為nil。

參考自以下文章
文章

九、@property中有哪些屬性關(guān)鍵字?/ @property 后面可以有哪些修飾符?

這個(gè)其實(shí)之前已經(jīng)都提到過了

  1. nonatomic ,atomic
  2. readonly,readwrite
    3 strong,weak,copy,assgin
    4 getter = name setter = name(setter不常用)
    上述4中可以理解為修改默認(rèn)getter或setter方法名。

十、weak屬性需要在dealloc中置nil么?

不需要。

在ARC環(huán)境無論是強(qiáng)指針還是弱指針都無需在 dealloc 設(shè)置為 nil , ARC 會(huì)自動(dòng)幫我們處理

十一、@synthesize和@dynamic分別有什么作用?

@property有兩個(gè)對(duì)應(yīng)的詞,一個(gè)是 @synthesize,一個(gè)是 @dynamic。如果 @synthesize和 @dynamic都沒寫,那么默認(rèn)的就是@syntheszie var = _var;
@synthesize 的語(yǔ)義是如果你沒有手動(dòng)實(shí)現(xiàn) setter 方法和 getter 方法,那么編譯器會(huì)自動(dòng)為你加上這兩個(gè)方法。
@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實(shí)現(xiàn),不自動(dòng)生成。(當(dāng)然對(duì)于 readonly 的屬性只需提供 getter 即可)。假如一個(gè)屬性被聲明為 @dynamic var,然后你沒有提供 @setter方法和 @getter 方法,編譯的時(shí)候沒問題,但是當(dāng)程序運(yùn)行到 instance.var = someVar,由于缺 setter 方法會(huì)導(dǎo)致程序崩潰;或者當(dāng)運(yùn)行到 someVar = var 時(shí),由于缺 getter 方法同樣會(huì)導(dǎo)致崩潰。編譯時(shí)沒問題,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動(dòng)態(tài)綁定。

十二、ARC下,不顯式指定任何屬性關(guān)鍵字時(shí),默認(rèn)的關(guān)鍵字都有哪些?

這個(gè)在上述問題中已經(jīng)有提到了這里再?gòu)?fù)制下吧

  1. 對(duì)應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是
    atomic,readwrite,assign
  2. 對(duì)于普通的 Objective-C 對(duì)象
    atomic,readwrite,strong

十三、用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?

這個(gè)算是對(duì)上面關(guān)于copy問題的補(bǔ)充。
因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對(duì)象還是不可對(duì)象,我本身持有的就是一個(gè)不可變的副本.
如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對(duì)象,如果這個(gè)可變對(duì)象在外部被修改了,那么會(huì)影響該屬性.
copy 此特質(zhì)所表達(dá)的所屬關(guān)系與 strong 類似。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)。 當(dāng)屬性類型為 NSString 時(shí),經(jīng)常用此特質(zhì)來保護(hù)其封裝性,因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè) NSMutableString 類的實(shí)例。這個(gè)類是 NSString 的子類,表示一種可修改其值的字符串,此時(shí)若是不拷貝字符串,那么設(shè)置完屬性之后,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改。所以,這時(shí)就要拷貝一份“不可變” (immutable)的字符串,確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)。只要實(shí)現(xiàn)屬性所用的對(duì)象是“可變的” (mutable),就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。

舉例說明:

定義一個(gè)以 strong 修飾的 array:

@property (nonatomic, readwrite, strong) NSArray *array;

    NSArray *array = @[@1,@2,@3];
    NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
    self.array = mutableArray;
    [mutableArray removeAllObjects];
    NSLog(@"strong = %@",self.array);
    
    [mutableArray addObjectsFromArray:array];
    self.array = [mutableArray copy];
    [mutableArray removeAllObjects];
    NSLog(@"copy = %@",self.array);

結(jié)果

2018-04-12 09:51:53.490577+0800 YFQResponseChain[45486:2129258] strong = (
)
2018-04-12 09:51:53.491013+0800 YFQResponseChain[45486:2129258] copy = (
    1,
    2,
    3
)

為了理解這種做法,首先要知道,兩種情況:

對(duì)非集合類對(duì)象的 copy 與 mutableCopy 操作;
對(duì)集合類對(duì)象的 copy 與 mutableCopy 操作
  1. 對(duì)非集合類對(duì)象的copy操作:
    在非集合類對(duì)象中:對(duì) immutable 對(duì)象進(jìn)行 copy 操作,是指針復(fù)制,mutableCopy 操作時(shí)內(nèi)容復(fù)制;對(duì) mutable 對(duì)象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。用代碼簡(jiǎn)單表示如下:
[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //深復(fù)制
[mutableObject copy] //深復(fù)制
[mutableObject mutableCopy] //深復(fù)制

如下代碼

    NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
    NSString *stringCopy = [string copy];
[string appendString:@"origion!"];
    NSLog(@"string = %@",stringCopy);
    NSLog(@"mutableString = %@",string);

結(jié)果

2018-04-12 09:52:04.788322+0800 YFQResponseChain[45486:2129258] string = origin
2018-04-12 09:52:04.788616+0800 YFQResponseChain[45486:2129258] mutableString = originorigion!
  1. 集合類對(duì)象的copy與mutableCopy
    集合類對(duì)象是指 NSArray、NSDictionary、NSSet ... 之類的對(duì)象。下面先看集合類immutable對(duì)象使用 copy 和 mutableCopy 的一個(gè)例子:
  NSMutableString *string = [NSMutableString stringWithString:@"origin"]
    NSArray *array1 = @[string, @[@"c", @"d"]];
    NSArray *copyArray = [array1 copy];
    NSMutableArray *mCopyArray = [array1 mutableCopy];
    [string appendString:@"origion!"];

結(jié)果

2018-04-12 09:52:04.789204+0800 YFQResponseChain[45486:2129258] mcopyArray = (
    "originorigion!",
        (
        c,
        d
    )
)

查看內(nèi)容,可以看到 copyArray 和 array 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的。說明 copy 操作進(jìn)行了指針拷貝,mutableCopy 進(jìn)行了內(nèi)容拷貝。但需要強(qiáng)調(diào)的是:此處的內(nèi)容拷貝,僅僅是拷貝 array 這個(gè)對(duì)象,array 集合內(nèi)部的元素仍然是指針拷貝。這和上面的非集合 immutable 對(duì)象的拷貝還是挺相似的,那么mutable對(duì)象的拷貝會(huì)不會(huì)類似呢?我們繼續(xù)往下,看 mutable 對(duì)象拷貝的例子:

NSMutableArray *array2 = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
    NSArray *copyArray2 = [array2 copy];
    NSMutableArray *mCopyArray2 = [array2 mutableCopy];

查看內(nèi)存,如我們所料,copyArray、mCopyArray和 array 的內(nèi)存地址都不一樣,說明 copyArray、mCopyArray 都對(duì) array 進(jìn)行了內(nèi)容拷貝。同樣,我們可以得出結(jié)論:

在集合類對(duì)象中,對(duì) immutable 對(duì)象進(jìn)行 copy,是指針復(fù)制, mutableCopy 是內(nèi)容復(fù)制;對(duì) mutable 對(duì)象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。但是:集合對(duì)象的內(nèi)容復(fù)制僅限于對(duì)象本身,對(duì)象元素仍然是指針復(fù)制。用代碼簡(jiǎn)單表示如下:

[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //單層深復(fù)制
[mutableObject copy] //單層深復(fù)制
[mutableObject mutableCopy] //單層深復(fù)制
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 出題者簡(jiǎn)介: 孫源(sunnyxx),目前就職于百度,負(fù)責(zé)百度知道 iOS 客戶端的開發(fā)工作,對(duì)技術(shù)喜歡刨根問底和...
    戈多_于勒閱讀 1,877評(píng)論 0 5
  • 1 年幼的時(shí)候,父親母親帶著我出門打工。那時(shí)候我還沒有記憶,唯一有映象的是父親的背上很舒服。 自我記事時(shí)起,那時(shí)候...
    F_先生閱讀 654評(píng)論 0 1
  • 用料:雞蛋6個(gè) 、白砂糖45克、葵花籽油40克、牛奶50克、低筋面粉113克 步驟:1、把雞蛋6個(gè)分成蛋清蛋黃放到...
    呂慧子閱讀 217評(píng)論 0 0
  • 什么是jQuery UI? jQuery UI是一個(gè)基于jQuery JavaScript的組件庫(kù),您可以使用它們...
    小賤賤9395閱讀 311評(píng)論 0 0

友情鏈接更多精彩內(nèi)容