iOS 基礎(chǔ)知識(shí)總結(jié) 更新中...

屬性

屬性與成員變量之間的關(guān)系

  • 屬性對(duì)成員變量擴(kuò)充了存儲(chǔ)方法
  • 屬性默認(rèn)會(huì)生成帶下劃線的成員變量
  • 聲明了成員變量不會(huì)生成屬性

成員變量地址可以根據(jù)實(shí)例的內(nèi)存地址偏移尋址。而屬性的讀寫都需要函數(shù)調(diào)用,相對(duì)更慢。self對(duì)應(yīng)類的成員變量的首地址

類別

利用OC的動(dòng)態(tài)運(yùn)行時(shí)為現(xiàn)有類添加方法,您可以在先有類中添加屬性,但是不能添加成員變量,但是我們的編譯器會(huì)把屬性自動(dòng)生成存儲(chǔ)方法和帶下劃線的成員變量,所以我們的聲明的屬性必須是@dynamic類型的,及需要重寫存儲(chǔ)方法。

#import "Person.h"

@interface Person (Ex)

@property(nonatomic, strong)    NSString *name;

- (void)setName:(NSString *)name; //***
- (NSString *)name;//***

- (void)method_one;
@end

分類方法實(shí)現(xiàn)中可以訪問原來(lái)類中聲明的成員變量。

類別的優(yōu)缺點(diǎn)

  • 不破壞原有的類的基礎(chǔ)之上擴(kuò)充方法。
  • 實(shí)現(xiàn)代碼之間的解偶

缺點(diǎn)

  • 無(wú)法在類別中添加新的實(shí)例變量,類別中沒有空間容納實(shí)例變量
  • 命名沖突
    類別無(wú)法添加實(shí)例變量的原因,從Runtime角度,我們發(fā)現(xiàn)的確沒有空間容納實(shí)力變量,為什么可以添加屬性,原因是屬性的本質(zhì)是實(shí)例變量+ getter方法 + setter方法;所以我們重寫了set/get方法

同名方法調(diào)用的優(yōu)先級(jí)為 分類 > 本類 > 父類

/* 
 *  Category Template //類別模版
 */
typedef struct objc_category *Category;

struct objc_category {
    char *category_name;
    char *class_name;
    struct objc_method_list *instance_methods;
    struct objc_method_list *class_methods;
    struct objc_protocol_list *protocols;
};

擴(kuò)展

沒有名字的類別

類別中創(chuàng)建的方法和屬性都是私有的,只有這個(gè)類對(duì)象可以使用

優(yōu)點(diǎn)

  • 可以添加實(shí)例變量
  • 可以更改讀寫權(quán)限(但是更改的權(quán)限的存儲(chǔ)方法只能是私有的)
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic,readonly)  NSString *name; //只有g(shù)etter方法,是公有的

@end


#import "Person.h"

@interface Person ()

@property (nonatomic, readwrite) NSString *name; //更改name的權(quán)限,但是setter方法是私有的不能被外部訪問

@end

@implementation Person

@end

類別和擴(kuò)展的區(qū)別

就category和extension的區(qū)別來(lái)看,我們可以推導(dǎo)出一個(gè)明顯的事實(shí),extension可以添加實(shí)例變量,而category是無(wú)法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會(huì)破壞類的內(nèi)部布局,這對(duì)編譯型語(yǔ)言來(lái)說是災(zāi)難性的)extension在編譯期決議,它就是類的一部分,但是category則完全不一樣,它是在運(yùn)行期決議的。extension在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類,它、extension伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。

  • extension一般用來(lái)隱藏類的私有信息,你必須有一個(gè)類的源碼才能為一個(gè)類添加extension,所以你無(wú)法為系統(tǒng)的類比如NSString添加extension,除非創(chuàng)建子類再添加extension。而category不需要有類的源碼,我們可以給系統(tǒng)提供的類添加category。
  • extension可以添加實(shí)例變量,而category不可以。
  • extension和category都可以添加屬性,但是category的屬性不能生成成員變量和getter、setter方法的實(shí)現(xiàn)。
    Extension
    在編譯器決議,是類的一部分,在編譯器和頭文件的@interface和實(shí)現(xiàn)文件里的@implement一起形成了一個(gè)完整的類。
    伴隨著類的產(chǎn)生而產(chǎn)生,也隨著類的消失而消失。
    Extension一般用來(lái)隱藏類的私有消息,你必須有一個(gè)類的源碼才能添加一個(gè)類的Extension,所以對(duì)于系統(tǒng)一些類,如NSString,就無(wú)法添加類擴(kuò)展
    Category
    是運(yùn)行期決議的
    類擴(kuò)展可以添加實(shí)例變量,分類不能添加實(shí)例變量
    原因:因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量會(huì)破壞類的內(nèi)部布局,這對(duì)編譯性語(yǔ)言是災(zāi)難性的。

非正式協(xié)議

創(chuàng)建一個(gè)NSObject的類別稱為"非正式協(xié)議"

正式協(xié)議

正式協(xié)議中可以方法,同時(shí)協(xié)議也是可以繼承的

@protocal MySuperDuberProtocol <MyParentProtocol>

@optional //可選

@required //必須要實(shí)現(xiàn)

@end

委托

委托就是某個(gè)對(duì)象指定另一個(gè)對(duì)象處理某些特定事物的設(shè)計(jì)模式

代理主要由三部分組成:

  • 協(xié)議:用來(lái)指定代理雙方可以做什么,必須做什么。
  • 代理:根據(jù)指定的協(xié)議,完成委托方需要實(shí)現(xiàn)的功能。
  • 委托:根據(jù)指定的協(xié)議,指定代理去完成什么功能。

這里用一張圖來(lái)闡述一下三方之間的關(guān)系:

代理使用原理

在iOS中代理的本質(zhì)就是代理對(duì)象內(nèi)存的傳遞和操作,我們?cè)谖蓄愒O(shè)置代理對(duì)象后,實(shí)際上只是用一個(gè)id類型的指針將代理對(duì)象進(jìn)行了一個(gè)弱引用。委托方讓代理方執(zhí)行操作,實(shí)際上是在委托類中向這個(gè)id類型指針指向的對(duì)象發(fā)送消息,而這個(gè)id類型指針指向的對(duì)象,就是代理對(duì)象。

代理原理

代理內(nèi)存管理

為什么我們?cè)O(shè)置代理屬性都使用weak呢?

我們定義的指針默認(rèn)都是__strong類型的,而屬性本質(zhì)上也是一個(gè)成員變量和set、get方法構(gòu)成的,strong類型的指針會(huì)造成強(qiáng)引用,必定會(huì)影響一個(gè)對(duì)象的生命周期,這也就會(huì)形成循環(huán)引用。


Block

原理:

//最基礎(chǔ)的結(jié)構(gòu)體實(shí)現(xiàn)
 void (^Blk)(void) = ^(void) {
     };
    
    Blk();

clang -rewrite-objc main.m后得到的結(jié)果

/*
// __block_impl 是 block 實(shí)現(xiàn)的結(jié)構(gòu)體
struct __block_impl
{
    void *isa; //說明block是一個(gè)對(duì)象來(lái)實(shí)現(xiàn)的
    int Flags; //按位承載 block 的附加信息;
    int Reserved; //保留變量
    void *FuncPtr; //函數(shù)指針,指向Block需要執(zhí)行的函數(shù)
};
*/

// __main_block_impl_0 是 block 實(shí)現(xiàn)的結(jié)構(gòu)體,也是 block 實(shí)現(xiàn)的入口
struct __main_block_impl_0 {
  struct __block_impl impl; //實(shí)現(xiàn)的結(jié)構(gòu)體變量及__block_impl
  struct __main_block_desc_0* Desc; //描述結(jié)構(gòu)體變量
  //結(jié)構(gòu)體的構(gòu)造函數(shù),初始化結(jié)構(gòu)體變量impl、Desc
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//__main_block_func_0最終需要執(zhí)行的函數(shù)代碼塊
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

     }

// __main_block_desc_0 是 block 的描述信息結(jié)構(gòu)體
static struct __main_block_desc_0 {
  size_t reserved; //結(jié)構(gòu)體信息保留字段
  size_t Block_size; //結(jié)構(gòu)體大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //定義一個(gè)結(jié)構(gòu)體變量,初始化結(jié)構(gòu)體,計(jì)算結(jié)構(gòu)體大小

int main(int argc, const char * argv[]) {

    void (*Blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)Blk)->FuncPtr)((__block_impl *)Blk);
}

isa 指向?qū)嵗龑?duì)象,表明 block 本身也是一個(gè) Objective-C 對(duì)象。block 的三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,即當(dāng)代碼執(zhí)行時(shí),isa 有三種值

  • impl.isa = &_NSConcreteStackBlock;
  • impl.isa = &_NSConcreteMallocBlock;
  • impl.isa = &_NSConcreteGlobalBlock;
  1. NSConcreteGlobalBlock 全局的靜態(tài) block,不會(huì)訪問任何外部變量。
  2. _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷毀。
  3. _NSConcreteMallocBlock 保存在堆中的 block,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷毀。

block 實(shí)現(xiàn)的執(zhí)行流程

main() >> 調(diào)用__main_block_impl_0構(gòu)造函數(shù)初始化結(jié)構(gòu)體__main__block_impl_0(__main_block_func_0, __main_block_desc_0_DATA) >> 得到的__main_block_impl_0類型變量賦值給Blk >> 執(zhí)行Blk->FuncPtr()函數(shù) >> END

帶參數(shù)的Block

    int intValue = 1;
    
    void (^Blk)(void) = ^(void) {
        NSLog(@"%d",intValue);
     };
    
    Blk();
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int intValue;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int intValue = __cself->intValue; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_r__t4y1n4fj5xlgntt308jvn7c80000gn_T_main_be769b_mi_0,intValue);
     }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {

    int intValue = 1;

    void (*Blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intValue));

    ((void (*)(__block_impl *))((__block_impl *)Blk)->FuncPtr)((__block_impl *)Blk);
}

原來(lái) block 通過參數(shù)值傳遞獲取到 intValue 變量,通過函數(shù)

__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)

保存到 __main_block_impl_0 結(jié)構(gòu)體的同名變量 intValue,通過代碼 int intValue = __cself->intValue; 取出 intValue,打印出來(lái)。

構(gòu)造函數(shù) __main_block_impl_0 冒號(hào)后的表達(dá)式 intValue(_intValue) 的意思是,用 _intValue 初始化結(jié)構(gòu)體成員變量 intValue。

有四種情況下應(yīng)該使用初始化表達(dá)式來(lái)初始化成員:

  1. 初始化const成員
  2. 初始化引用成員
  3. 當(dāng)調(diào)用基類的構(gòu)造函數(shù),而它擁有一組參數(shù)時(shí)
  4. 當(dāng)調(diào)用成員類的構(gòu)造函數(shù),而它擁有一組參數(shù)時(shí)

KVC、KVO

KVC/KVO是觀察者模式的一種實(shí)現(xiàn),在Cocoa中是以被萬(wàn)物之源NSObject類實(shí)現(xiàn)的NSKeyValueCoding/NSKeyValueObserving非正式協(xié)議的形式被定義為基礎(chǔ)框架的一部分。從協(xié)議的角度來(lái)說,KVC/KVO本質(zhì)上是定義了一套讓我們?nèi)プ袷睾蛯?shí)現(xiàn)的方法.當(dāng)然,KVC/KVO實(shí)現(xiàn)的根本是Objective-C的動(dòng)態(tài)性和runtime

KVC定義了一種按名稱(字符串)訪問對(duì)象的機(jī)制,而不是訪問器

KVC的實(shí)現(xiàn)細(xì)節(jié)

-(void)setValue:(id)value forKey:(NSString *)key;

  1. 首先搜索set方法,有就直接賦值
  2. 如果上面的 setter 方法沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly
    • 返回 NO,則執(zhí)行setValue:forUNdefinedKey:
    • 返回 YES,則按<key>,<isKey>,<key>,<isKey>的順序搜索成員
  3. 還沒有找到的話,就調(diào)用setValue:forUndefinedKey:
 // 允許直接訪問實(shí)例變量,默認(rèn)返回YES。如果某個(gè)類重寫了這個(gè)方法,且返回NO,則KVC不可以訪問該類。
 + (BOOL)accessInstanceVariablesDirectly
 
// 如果Key不存在,且沒有KVC無(wú)法搜索到任何和Key有關(guān)的字段或者屬性,則會(huì)調(diào)用這個(gè)方法,默認(rèn)是拋出異常
- (id)valueForUndefinedKey:(NSString *)key;
// 如果Key不存在,且沒有KVC無(wú)法搜索到任何和Key有關(guān)的字段或者屬性,則會(huì)調(diào)用這個(gè)方法,默認(rèn)是拋出異常
- (id)valueForUndefinedKey:(NSString *)key;

-(id)valueForKey:(NSString *)key;

  1. 首先查找 getter 方法,找到直接調(diào)用。如果是 bool、int、float 等基本數(shù)據(jù)類型,會(huì)做 NSNumber 的轉(zhuǎn)換。

  2. 如果沒查到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly

    • 返回 NO,則執(zhí)行valueForUndefinedKey:
    • 返回 YES,則按<key>,<isKey>,<key>,<isKey>的順序搜索成員
  3. 還沒有找到的話,調(diào)用valueForUndefinedKey:

KVC 與點(diǎn)語(yǔ)法比較

用 KVC 訪問屬性和用點(diǎn)語(yǔ)法訪問屬性的區(qū)別:

  • 用點(diǎn)語(yǔ)法編譯器會(huì)做預(yù)編譯檢查,訪問不存在的屬性編譯器會(huì)報(bào)錯(cuò),但是用 KVC 方式編譯器無(wú)法做檢查,如果有錯(cuò)誤只能運(yùn)行的時(shí)候才能發(fā)現(xiàn)(crash)。

  • 相比點(diǎn)語(yǔ)法用 KVC 方式 KVC 的效率會(huì)稍低一點(diǎn),但是靈活,可以在程序運(yùn)行時(shí)決定訪問哪些屬性。

  • 用 KVC 可以訪問對(duì)象的私有成員變量。

KVO 實(shí)現(xiàn)原理

當(dāng)某個(gè)類的對(duì)象第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的 setter 方法。 派生類在被重寫的 setter 方法實(shí)現(xiàn)真正的通知機(jī)制,就如前面手動(dòng)實(shí)現(xiàn)鍵值觀察那樣。這么做是基于設(shè)置屬性會(huì)調(diào)用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機(jī)制。當(dāng)然前提是要通過遵循 KVO 的屬性設(shè)置方式來(lái)變更屬性值,如果僅是直接修改屬性對(duì)應(yīng)的成員變量,是無(wú)法實(shí)現(xiàn) KVO 的。 同時(shí)派生類還重寫了 class 方法以“欺騙”外部調(diào)用者它就是起初的那個(gè)類。然后系統(tǒng)將這個(gè)對(duì)象的 isa 指針指向這個(gè)新誕生的派生類,因此這個(gè)對(duì)象就成為該派生類的對(duì)象了,因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用重寫的 setter,從而激活鍵值通知機(jī)制。此外,派生類還重寫了 dealloc 方法來(lái)釋放資源。

派生類 NSKVONotifying_Person 剖析:

在這個(gè)過程,被觀察對(duì)象的 isa 指針從指向原來(lái)的 Person 類,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_Person 類,來(lái)實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽。

所以當(dāng)我們從應(yīng)用層面上看來(lái),完全沒有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì) KVO 的底層實(shí)現(xiàn)過程,讓我們誤以為還是原來(lái)的類。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為 NSKVONotifying_Person 的類(),就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè) KVO 的那段代碼時(shí)程序就崩潰,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽的時(shí)候動(dòng)態(tài)創(chuàng)建了名為 NSKVONotifying_Person 的中間類,并指向這個(gè)中間類了。

因而在該對(duì)象上對(duì) setter 的調(diào)用就會(huì)調(diào)用已重寫的 setter,從而激活鍵值通知機(jī)制。這也是 KVO 回調(diào)機(jī)制,為什么都俗稱 KVO 技術(shù)為黑魔法的原因之一吧:內(nèi)部神秘、外觀簡(jiǎn)潔。

子類 setter 方法剖析:

KVO 在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey:,通知系統(tǒng)該 keyPath 的屬性值即將變更。 當(dāng)改變發(fā)生后,didChangeValueForKey: 被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更。 之后,observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。

重寫觀察屬性的 setter 方法這種方式是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。 KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

- (void)setName:(NSString *)newName
{
    [self willChangeValueForKey:@"name"];    // KVO在調(diào)用存取方法之前總調(diào)用
    [super setValue:newName forKey:@"name"]; // 調(diào)用父類的存取方法
    [self didChangeValueForKey:@"name"];     // KVO在調(diào)用存取方法之后總調(diào)用
}

總結(jié): KVO 的本質(zhì)就是監(jiān)聽對(duì)象的屬性進(jìn)行賦值的時(shí)候有沒有調(diào)用 setter 方法

系統(tǒng)會(huì)動(dòng)態(tài)創(chuàng)建一個(gè)繼承于 Person 的 NSKVONotifying_Person

person 的 isa 指針指向的類 Person 變成 NSKVONotifying_Person,所以接下來(lái)的 person.age = newAge 的時(shí)候,他調(diào)用的不是 Person 的 setter 方法,而是 NSKVONotifying_Person(子類)的 setter 方法

重寫NSKVONotifying_Person的setter方法:[super setName:newName]

#import "ViewController.h"
#import "Student.h"


@interface ViewController ()
{
    Student             *_student;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _student = [[Student alloc] init];
    _student.stuName = @"oldName_hu";

    // 1.給student對(duì)象的添加觀察者,觀察其stuName屬性
    [_student addObserver:self forKeyPath:@"stuName" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

    // 此時(shí),stuName發(fā)生了變化
    _student.stuName = @"newName_wang";
}

// stuName發(fā)生變化后,觀察者(self)立馬得到通知。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    // 最好判斷目標(biāo)對(duì)象object和屬性路徑keyPath
    if(object == _student && [keyPath isEqualToString:@"stuName"])
    {
        NSLog(@"----old:%@----new:%@",change[@"old"],change[@"new"]);
    }else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}


- (void)dealloc
{
    // 移除觀察者
    [_student removeObserver:self forKeyPath:@"stuName"];
}

@end
@interface Target : NSObject
{
    int age;
}

// for manual KVO - age
- (int) age;
- (void) setAge:(int)theAge;

@end

@implementation Target

- (id) init
{
    self = [super init];
    if (nil != self)
    {
        age = 10;
    }
    
    return self;
}

// for manual KVO - age
- (int) age
{
    return age;
}

- (void) setAge:(int)theAge
{
    [self willChangeValueForKey:@"age"];
    age = theAge;
    [self didChangeValueForKey:@"age"];
}

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

@end

///
/**
 *  添加觀察者
 *
 *  @param observer 觀察者
 *  @param keyPath  被觀察的屬性名稱
 *  @param options  觀察屬性的新值、舊值等的一些配置(枚舉值,可以根據(jù)需要設(shè)置,例如這里可以使用兩項(xiàng))
 *  @param context  上下文,可以為nil。
 */
[person addObserver: self
             forKeyPath: @"age"
                options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                context: nil];


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    Person *per = object;
    NSLog(@"keyPath: %@ object: %ld",keyPath, (long)per.age);
}


最后編輯于
?著作權(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)容

  • 喜歡就關(guān)注我唄! 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的...
    iOS白水閱讀 1,174評(píng)論 0 2
  • 1、category和extension的區(qū)別 category是分類,可以為類增加自定義方法 extension...
    大猿媛閱讀 402評(píng)論 1 1
  • category 和 extension 的區(qū)別 -分類有名字,類擴(kuò)展沒有分類名字,是一種特殊的分類 -分類只能擴(kuò)...
    白羊的羊閱讀 455評(píng)論 0 1
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,593評(píng)論 30 472
  • 今天老師布置了家庭作業(yè),請(qǐng)孩子和家長(zhǎng)共同完成,這已經(jīng)不是第一次了,但是和以往不一樣的是這次是由孩子自己編故事,家...
    九月桔閱讀 336評(píng)論 2 0

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