Objective-C(一)-對(duì)象胞谭、屬性、方法

1.對(duì)象模型

Objective-C是一門面向?qū)ο蟮恼Z言男杈,對(duì)象是我們編程的基本單元丈屹,所有的操作都是通過對(duì)象。對(duì)象其實(shí)是對(duì) 數(shù)據(jù)行為 的封裝伶棒。在OC中旺垒,數(shù)據(jù)的載體就是實(shí)例變量彩库,我們可以通過屬性便捷的訪問到實(shí)例變量行為其實(shí)就是對(duì)象的方法先蒋,也可以稱為發(fā)消息骇钦,方法內(nèi)部可以傳遞數(shù)據(jù)和操作數(shù)據(jù)。

平時(shí)我們聲明一個(gè)對(duì)象竞漾,例如:NSString *string = @"zzy"; 這條語句的意思是司忱,創(chuàng)建了一個(gè)NSString類型的對(duì)象實(shí)例,實(shí)例的內(nèi)容是zzy畴蹭,并返回這個(gè)實(shí)例的內(nèi)存地址給string這個(gè)變量保存,之后我們就可以通過變量string來操作這個(gè)實(shí)例鳍烁。學(xué)過C語言的都知道叨襟,其實(shí)*string就是指這是一個(gè)NSString類型的指針,所以OC對(duì)象的本質(zhì)其實(shí)就是指向某塊內(nèi)存地址的指針幔荒。

對(duì)象的結(jié)構(gòu)

在OC中每個(gè)對(duì)象都是一個(gè)類的實(shí)例糊闽,對(duì)象的結(jié)構(gòu)如下:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

可以看到對(duì)象是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體當(dāng)中有一個(gè)Class類型的成員變量isa爹梁。Class的定義為typedef struct objc_class *Class;右犹,這是一個(gè)指向objc_class結(jié)構(gòu)體的一個(gè)結(jié)構(gòu)體指針。而objc_class其實(shí)就是對(duì)象所屬類的原型:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

可以看到Class中也存放了一個(gè)isa指針姚垃,所以Class也是一個(gè)對(duì)象念链,被稱為類對(duì)象。同時(shí)Class結(jié)構(gòu)體當(dāng)中還存著:指向父類的super_class指針积糯、類名掂墓、版本、實(shí)例對(duì)象的大小看成、實(shí)例變量列表君编、方法列表、緩存川慌、協(xié)議等信息吃嘿。通過類創(chuàng)建出來的實(shí)例對(duì)象所擁有的屬性,方法梦重,協(xié)議等都是存儲(chǔ)在類結(jié)構(gòu)體當(dāng)中兑燥。

實(shí)例對(duì)象的isa指針指向其所屬的類,類結(jié)構(gòu)體當(dāng)中存放著對(duì)象的屬性和方法列表忍饰。類對(duì)象的isa指針指向其所屬的元類贪嫂,元類中存放著類對(duì)象的方法列表(類方法),而元類內(nèi)部也有一個(gè)isa指針艾蓝,指向的是基類的元類力崇,而基類的元類的isa指針指向自身斗塘。用一個(gè)經(jīng)典的圖類表示整個(gè)關(guān)系鏈路:

image

不能向編譯后得到的類中增加實(shí)例變量:因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例的內(nèi)存大小已經(jīng)確定亮靴,同時(shí)runtime 會(huì)調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用馍盟。所以不能向存在的類中添加實(shí)例變量。

運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量茧吊,調(diào)用 class_addIvar 函數(shù)贞岭。但是得在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前搓侄,否則類一旦注冊(cè)到runtime中后就不能改變實(shí)例變量了瞄桨。

2. 屬性

對(duì)象中的數(shù)據(jù)是通過實(shí)例變量來保存的,OC提供了一種便捷的訪問實(shí)例變量的方式:屬性讶踪。屬性的本質(zhì)就是包括實(shí)例變量 + setter方法 + getter方法芯侥。實(shí)例變量的值保存在對(duì)象內(nèi)部,通過“偏移量(offset)”來保存乳讥,即:該變量距離對(duì)象內(nèi)存區(qū)域的起始地址的距離柱查,這個(gè)是通過硬編碼來標(biāo)識(shí)的。當(dāng)我們聲明一個(gè)屬性之后云石,系統(tǒng)會(huì)自動(dòng)幫我們生成一個(gè)成員變量和屬性唉工,這個(gè)成員變量和屬性的描述(類型,名稱等)分別存放在類的ivar_listproperty_list當(dāng)中汹忠,并且生成setter方法和getter方法追加到method_list當(dāng)中淋硝,然后計(jì)算屬性在對(duì)象內(nèi)部的偏移量,并實(shí)現(xiàn)setter方法和getter方法错维。

聲明屬性的方式:@property (nonatomic, readwrite, copy) NSString *name; 這個(gè)時(shí)候編譯器會(huì)自動(dòng)幫我們合成實(shí)例變量_name奖地。默認(rèn)的實(shí)例變量名為屬性名前面加下劃線。如果不想用系統(tǒng)默認(rèn)的實(shí)例變量名赋焕,也可以通過@synthesize關(guān)鍵字手動(dòng)指定實(shí)例變量名参歹,例如@synthesize name = _myName; 那么我們的實(shí)例變量就變成了_myName,不過一般為了規(guī)范沒有人這么做隆判。

如果不想讓系統(tǒng)自動(dòng)幫我們合成實(shí)例變量和setter犬庇、getter方法,也可以通過關(guān)鍵字@dynamic聲明(例如:@dynamic name;)侨嘀,然后自己手動(dòng)去合成實(shí)例變量和相關(guān)存取方法臭挽。如果沒有實(shí)現(xiàn)這些合成方法的話,那么是無法正常使用屬性的咬腕。

目前使用@synthesize的一般場(chǎng)景:

  • 當(dāng)我們手動(dòng)實(shí)現(xiàn)了settergetter方法的時(shí)候欢峰,系統(tǒng)默認(rèn)我們自己管理屬性,所以就不再幫我們合成實(shí)例變量了,這個(gè)時(shí)候需要用@synthesize手動(dòng)合成實(shí)例變量纽帖,不然編譯器就會(huì)報(bào)錯(cuò)宠漩。

  • 當(dāng)重寫父類屬性的時(shí)候,在子類中需要用@synthesize手動(dòng)合成實(shí)例變量懊直,否則無法使用扒吁。

  • 使用了@dynamic的時(shí)候。

屬性包含的三種語義:

atomic && nonatomic :原子性 && 非原子性

在聲明屬性的時(shí)候室囊,編譯器默認(rèn)的屬性語義是atomic原子性的雕崩。atomicnonatomic的區(qū)別是前者在setter方法賦值的時(shí)候會(huì)進(jìn)行加鎖保證賦值過程的完整性(安全性),而后者不會(huì)使用同步鎖融撞。在iOS中盼铁,由于頻繁加鎖會(huì)導(dǎo)致性能問題,而且即使采用了atomic尝偎,在多線程操作的情況下捉貌,也并不能保證線程的安全性。如果要保證線程安全冬念,需要專門進(jìn)行其他加鎖機(jī)制處理。所以一般情況下牧挣,我們平時(shí)聲明屬性用的都是nonatomic急前。

readwrite && readonly :可讀可寫 && 只讀

readwrite 表示屬性可讀可寫。readonly表示屬性只能讀取不能修改瀑构,一般用于在.h中對(duì)外暴露的屬性不想被別人修改時(shí)這么聲明裆针,然后在.m的extension中再重新定義為可讀可寫。編譯器默認(rèn)的屬性語義為readwrite寺晌。

內(nèi)存管理語義:strong世吨、weak、copy呻征、assign耘婚、unsafe_unretained

  • strong 表示對(duì)屬性所指對(duì)象的一種強(qiáng)引用的關(guān)系。當(dāng)聲明為strong時(shí)陆赋,在setter方法中會(huì)先持有新值沐祷,再釋放舊值,然后再把新值賦值給實(shí)例變量攒岛。
    eg:
@property (nonatomic, strong) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
   // [array retain];
   // [_array release];
    _array = array;
}
  • weak 表示對(duì)屬性所指對(duì)象的一種弱引用的關(guān)系赖临。當(dāng)聲明為weak時(shí),在setter方法中即不會(huì)持有新值灾锯,也不會(huì)釋放舊值兢榨,只是進(jìn)行一次簡單的賦值操作。但是當(dāng)屬性所指的對(duì)象被銷毀時(shí),屬性值會(huì)自動(dòng)置為nil 比較安全吵聪。
    eg:
@property (nonatomic, weak) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
    _array = array;
}
  • copy 類似于strong凌那,也表示對(duì)屬性所指對(duì)象的一種強(qiáng)引用的關(guān)系,不同的是暖璧,copy語義在setter方法中并不是直接持有新值案怯,而是會(huì)拷貝出一份不可變的副本持有,然后再賦值給實(shí)例變量澎办。
    eg:
@property (nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name {
    NSString *copyName = [name copy];
    //[_name release];
    _name = copyName;
    //[copyName release]
}

copy語義一般是用于那些具有可變子類的類型如:NSArray嘲碱、NSDictionaryNSString等局蚀。這些類族都有其對(duì)應(yīng)的可變子類麦锯,如果聲明一個(gè)不可變的NSString類型的屬性,由于父類指針可以指向子類對(duì)象琅绅,在給屬性賦值的時(shí)候扶欣,傳遞給setter方法的值有可能是一個(gè)可變子類NSMutableString的實(shí)例對(duì)象,那么在該屬性值賦值完成后千扶,由于屬性所指的對(duì)象其實(shí)是個(gè)可變的字符串料祠,就有可能被外界所篡改。所以為了保證屬性的安全澎羞,在賦值的時(shí)候需要先copy出一份不可變的對(duì)象髓绽,然后再賦值。

  • assign 表示在 setter 方法賦值時(shí)只會(huì)進(jìn)行簡單的賦值操作妆绞,只用于修飾基本類型的數(shù)據(jù)(例如NSInteger顺呕、CGFloat等)。

  • unsafe_unretained 語義類似于assign括饶,不同的是它用于修飾對(duì)象類型株茶。如同它的字面意義一樣,它不會(huì)持有屬性所指的對(duì)象(類似于weak)图焰,但是當(dāng)屬性所指的對(duì)象被銷毀時(shí)启盛,屬性值不會(huì)自動(dòng)置為nil,所以它并不安全技羔,可能會(huì)導(dǎo)致野指針驰徊。

3. 方法

在OC中,方法又被稱為發(fā)消息堕阔,對(duì)象需要調(diào)用方法來傳遞數(shù)據(jù)棍厂,而方法是什么呢?例如一個(gè)方法:[self doSomething:@"something"];超陆,編譯器會(huì)將這個(gè)方法轉(zhuǎn)為如下的C語言函數(shù):
objc_msgSend(self, @selector(doSomething:), @"something");

objc_msgSend(id self, SEL cmd, ...) 這個(gè)函數(shù)就是OC消息傳遞的核心函數(shù)牺弹。我們平時(shí)調(diào)用的方法最終都會(huì)轉(zhuǎn)為這個(gè)函數(shù)調(diào)用浦马。這個(gè)函數(shù)接受兩個(gè)及以上的參數(shù),分別是:消息的接收者张漂、消息的簽名selector晶默、消息的參數(shù)。消息的接收者就是該方法的調(diào)用者航攒,消息的簽名是一個(gè)SEL類型的數(shù)據(jù)磺陡,可以理解為方法名的包裝,參數(shù)就是調(diào)用方法所傳遞的參數(shù)漠畜,按順序傳入币他。

當(dāng)調(diào)用這個(gè)函數(shù)后,objc_msgSend會(huì)根據(jù)當(dāng)前消息接收者的isa指針找到其所屬的類憔狞,然后在類的方法列表中根據(jù)selector的名稱找到對(duì)應(yīng)的方法并執(zhí)行蝴悉,同時(shí)會(huì)將查找的結(jié)果緩存起來以供下次查找時(shí)快速的執(zhí)行。如果找不到瘾敢,就會(huì)根據(jù)super_class 指針沿著繼承體系一直往上查找拍冠,直到找到合適的方法之后跳轉(zhuǎn)到方法的實(shí)現(xiàn)并執(zhí)行。如果直到找到基類還是找不到對(duì)應(yīng)方法的話簇抵,那就開始執(zhí)行消息轉(zhuǎn)發(fā)機(jī)制庆杜。如果消息轉(zhuǎn)發(fā)的過程中也沒有找到的話,那就拋出異常程序終止碟摆。異常信息是常見的unrecognized selector send to instace xxxx

消息轉(zhuǎn)發(fā)

上面在介紹方法調(diào)用的實(shí)現(xiàn)流程時(shí)說到了消息轉(zhuǎn)發(fā)欣福,消息轉(zhuǎn)發(fā)是發(fā)生在當(dāng)給一個(gè)對(duì)象發(fā)送沒有實(shí)現(xiàn)的方法時(shí)會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)。例如我們給一個(gè)對(duì)象發(fā)送沒有實(shí)現(xiàn)的消息:

[obj performSelector:@selector(doWork:array:) withObject:@"somthing" withObject:@[@"work"]];

此時(shí)消息轉(zhuǎn)發(fā)一共分為三個(gè)階段:

第一個(gè)階段:動(dòng)態(tài)方法解析

runtime會(huì)先詢問對(duì)象所屬的類焦履,看其能否動(dòng)態(tài)的添加方法以處理當(dāng)前未處理的消息。處理的方法有兩個(gè):

實(shí)例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel

類方法:+ (BOOL)resolveClassMethod:(SEL)sel

方法的參數(shù)就是當(dāng)前消息的selector雏逾,返回值是來標(biāo)識(shí)當(dāng)前的類是否能動(dòng)態(tài)的添加方法來處理這個(gè)selector
eg:

@interface MessageInvokeObj : NSObject

@end

@implementation MessageInvokeObj

void doWork(id self, SEL cmd, NSString *work, NSArray *array) {
    NSLog(@"work = %@, array = %@", work, array);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"doWork:array:"]) {
        class_addMethod(self, sel, (IMP)doWork, "v@:@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

通過class_addMethod函數(shù)動(dòng)態(tài)的添加一個(gè)方法來處理當(dāng)前的消息嘉裤。此時(shí)動(dòng)態(tài)添加方法成功,消息轉(zhuǎn)發(fā)結(jié)束栖博。

第二個(gè)階段:備援接收者

如果第一個(gè)階段無法處理該消息的話屑宠,runtime會(huì)詢問當(dāng)前的類能否把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象去處理,如果可以的話仇让,就返回這個(gè)對(duì)象典奉,否則就返回nil。

處理方法:- (id)forwardingTargetForSelector:(SEL)aSelector

eg:

@interface InvokeMethodObj : NSObject

@end

@implementation InvokeMethodObj

- (void)doWork:(NSString *)work array:(NSArray *)array {
    NSLog(@"work = %@, array = %@", work, array);
}

@end

@interface MessageInvokeObj : NSObject

@end

@implementation MessageInvokeObj

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *selString = NSStringFromSelector(aSelector);
    if ([selString isEqualToString:@"doWork:array:"]) {
        InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
        return obj;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

第三個(gè)階段: forwardInvocation

最后一個(gè)階段丧叽,runtime會(huì)將這個(gè)消息的所有信息封裝到NSInvocation對(duì)象當(dāng)中卫玖,包括消息的接收者target、消息的selector以及消息的所有參數(shù)踊淳。最后一次詢問接收者能否處理假瞬,如果不能將拋出異常陕靠。如果可以,直接把消息指派給目標(biāo)對(duì)象脱茉。

先返回正確的方法簽名:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

然后執(zhí)行消息轉(zhuǎn)發(fā):- (void)forwardInvocation:(NSInvocation *)anInvocation

eg:

@interface MessageInvokeObj : NSObject

@end

@implementation MessageInvokeObj

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *selString = NSStringFromSelector(aSelector);
    if ([selString isEqualToString:@"doWork:array:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
    if ([obj respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:obj];
    }
}

@end

OC對(duì)象模型大概就是這些剪芥,要想更深入的了解,需要讀下runtime的源碼琴许。下一篇寫下OC的內(nèi)存管理...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末税肪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榜田,更是在濱河造成了極大的恐慌益兄,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件串慰,死亡現(xiàn)場(chǎng)離奇詭異偏塞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)邦鲫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門灸叼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庆捺,你說我怎么就攤上這事古今。” “怎么了滔以?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵捉腥,是天一觀的道長。 經(jīng)常有香客問我你画,道長抵碟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任坏匪,我火速辦了婚禮拟逮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘适滓。我一直安慰自己敦迄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布凭迹。 她就那樣靜靜地躺著罚屋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗅绸。 梳的紋絲不亂的頭發(fā)上脾猛,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音鱼鸠,去河邊找鬼尖滚。 笑死喉刘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漆弄。 我是一名探鬼主播睦裳,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼撼唾!你這毒婦竟也來了廉邑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤倒谷,失蹤者是張志新(化名)和其女友劉穎蛛蒙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渤愁,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牵祟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖格。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诺苹。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖雹拄,靈堂內(nèi)的尸體忽然破棺而出收奔,到底是詐尸還是另有隱情,我是刑警寧澤滓玖,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布坪哄,位于F島的核電站,受9級(jí)特大地震影響势篡,放射性物質(zhì)發(fā)生泄漏翩肌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一禁悠、第九天 我趴在偏房一處隱蔽的房頂上張望念祭。 院中可真熱鬧,春花似錦绷蹲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至若厚,卻和暖如春拦英,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背测秸。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工疤估, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灾常,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓铃拇,卻偏偏與公主長得像钞瀑,于是被迫代替她去往敵國和親慷荔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雕什,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345