前两年学习过程当中陆陆续续整理的知识点,今天开始迁移到掘金。因为当年在翻阅国产技术书籍时,发现知识点有很多错误,踩了很多坑,固然可能仍然有错误和遗漏,欢迎指正~html
KVC容许以字符串形式间接操做对象的属性,全称为Key Value Coding,即键值编码。ios
- (void)setValue:(nullable id)value forKey:(NSString *)key;
复制代码
-set<Key>:
代码经过setter方法赋值。(勘误1)+(BOOL)accessInstanceVariablesDirectly
方法,若是你重写了该方法并使其返回NO,则KVC下一步会执行setValue:forUndefinedKey:
,默认抛出异常。_<key>
,_<isKey>
,<key>
,<isKey>
的成员变量。setValue:forUndefinedKey:
方法,默认抛出异常。勘误1:经验证,在查找`-set<Key>:`后,若是没有找到,还会去查找`-_set<Key>:`方法,而后才会进入步骤2,感谢@QXCloud 的指正~ 数组
- (nullable id)valueForKey:(NSString *)key;
复制代码
-get<Key>
,-<key>
,-is<Key>
代码经过getter方法获取值。-countOf<Key>
,-objectIn<Key>AtIndex:
和-<key>AtIndexes:
方法,若是count方法和另外两个中的一个被找到,返回一个能响应全部NSArray方法的代理集合,简单来讲就是能够当NSArray用。-countOf<Key>
,-enumeratorOf<Key>
和-memberOf<Key>:
方法,若是三个都能找到,返回一个能响应全部NSSet方法的代理集合,简单来讲就是能够当NSSet使用。_<key>
,_<isKey>
,<key>
,<isKey>
的成员变量,返回该成员变量的值。valueForUndefinedKey:
方法,默认抛出异常。- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
复制代码
KVC不只能够操做对象属性,还能够操做对象的“属性链”。如Person中有一个类型为Date的birthday属性,而Date中又有year,month,day等属性,那么Person能够直接经过birthday.year这种Key Path来操做birthday的year属性。安全
从上述机制能够看出,在没有setter方法时,会检查+(BOOL)accessInstanceVariablesDirectly
来决定是否搜索类似成员变量,所以只须要重写该方法并返回NO便可。bash
开发中可能有些须要设定对象属性不能够设置某些值,此时就须要检验Value的可用性,经过以下方法数据结构
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
复制代码
这个方法的默认实现是去探索类里面是否有一个这样的方法-(BOOL)validate<Key>:error:
,若是有这个方法,就调用这个方法来返回,没有的话就直接返回YES。 注意:在KVC设值的时候,并不会主动调用该方法去校验,须要开发者手动调用校验,意味着即便实现此方法,也能够赋值成功。app
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
复制代码
没有找到相关key,会抛出NSUndefinedKeyException
异常,使用KVC时通常须要重写这两个方法。编辑器
- (void)setNilValueForKey:(NSString *)key;
复制代码
当给基础类型的对象属性设置nil时,会抛出NSInvalidArgumentException
异常,通常也须要重写。ide
- (void)setValuesForKeysWithDictionary:
字典转model,如股票字段。valueForKey:
将会被传递给容器中的每个对象,而不是容器自己进行操做,由此咱们能够有效的提取容器中每一个对象的指定属性值集合。@avg
,@count
,@max
,@min
@sum
。KVO提供了一种机制(基于NSKeyValueObserving协议,全部Object都实现了此协议)能够供观察者监听对象属性的变化并接收通知,全称为Key Value Observing,即键值监听。模块化
经常使用API
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
复制代码
观察者重写
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
复制代码
当观察者对对象A注册一个监听时,系统此时会动态建立一个名为NSKVONotifying_A的新类,该类继承自对象A本来的类,并重写被观察者的setter方法,在原setter方法的调用先后通知观察者值的改变。而后将对象A的isa指针(isa指针告诉Runtime这个对象的类是什么)指向NSKVONotifying_A这个新类,那么对象A就变成了新建立新类的实例。 不只如此,Apple还重写了-class
方法来隐藏该新类,让人们觉得注册先后对象A的类并无改变,但实际上若是手动新建一个NSKVONotifying_A类,在观察者运行到注册时,便会引发重复类崩溃。
从上述实现原理来看,很明显能够知道,若是没有经过setter赋值,直接赋值该成员变量是不会触发KVO机制的,可是KVC除外,这也侧面证实了KVC与KVO是有内在联系的。
alloc负责为对象的全部成员变量分配内存空间,而且为各成员变量重置为默认值,如int型为0,BOOL型为NO,指针型变量为nil。 仅仅分配空间还不够,还须要init来对对象执行初始化操做,才可使用它。若是只调用alloc不调用init,也能运行,但可能会出现未知结果。
所作的事情与alloc差很少,也是分配内存和初始化。
new只能使用默认的init初始化,而alloc可使用其余初始化方法,由于显示调用总比隐式调用好,因此每每使用alloc/init来初始化。
NSString *A = @"hello";
NSString *B = @"hello";
NSString *C = [NSString stringWithFormat:@"hello"];
NSString *D = [NSString stringWithFormat:@"hello"];
复制代码
@"hello"
位于常量池中,可重复使用,其中A和B指向的都是同一分内存地址。 而[NSString stringWithFormat:@"hello"]
是在运行时建立出来的,保存在运行时内存(即堆内存),其中C和D指向的内存地址不一样,与A、B也不相同。
具体可分为四类:线程安全、读写权限、内存管理和指定读写方法。
若是不写该类修饰符,默认就是atomic。二者最大的区别就是决定编译器生成的getter/setter方法是否属于原子操做,若是本身写了getter/setter方法,此时用什么都同样。 对于atomic来讲,getter/setter方法增长了锁来确保操做的完整性,不受其余线程影响。例如线程A的getter方法运行到一半,线程B调用setter方法,那么线程A仍是能获得一个完整的Value。 而对于nonatomic来讲,多个线程能同时访问操做,就没法保证是不是完整的Value,还会引起脏数据。可是nonatomic更快,开发中每每在可控状况下安全换效率。
注意:atomic并不能彻底保证线程安全,只能保证数据操做的线程安全,例如线程A使用getter方法,同时线程B、C使用setter方法,那最后线程A获取到的值有三种可能:原始值、B set的值或者C set的值;又例如线程A使用getter方法,线程B同时调用release方法,因为release方法并无加锁,因此有可能会致使cash。
readonly只读属性,只会生成getter方法,不会生成setter方法。 readwrite读写属性,会生成getter/setter方法,默认是该修饰符。
strong强引用,适用于对象,引用计数+1,对象默认是该修饰符。 weak弱引用,为这种属性设置新值时,设置方法既不释放旧值,也不保留新值,不会使引用计数加1。当所指对象被销毁时,指针会自动被置为nil,防止野指针。
assgin适用于基础数据类型,如NSIntger,CGFloat,int等,只进行简单赋值,基础数据类型默认是该修饰符。若是用此修饰符修饰对象,对象被销毁时,并不会置空,会形成野指针。 copy是为了解决上下文的异常依赖,实际赋值类型不可变对象时,浅拷贝;可变对象时,深拷贝。
给getter/setter方法起别名,能够不一致,而且能够与其余属性的getter/setter重名,例如Person类中定义以下
@property (nonatomic, copy, setter=setNewName:, getter=oldName) NSString *name;
@property (nonatomic, copy) NSString *oldName;
复制代码
那么此时p1.oldName
始终是_name
的值,而若是声明的顺序交换,此时p1.oldName
就是_oldName
的值了,若是想获得_name
的值,使用p1.name
便可,可是此时不能使用-setName:
。因此别名都是有意义且不重复的,避免一些想不到的问题。
strong是浅拷贝,仅拷贝指针并增长引用计数;而copy在对于实际赋值对象是可变对象时,是深拷贝。不可变对象使用copy修饰,如NSString、NSArray、NSSet等;可变对象使用strong修饰,如NSMutableString、NSMutableArray、NSMutableSet等,这是为何呢? 因为父类属性能够指向子类对象,试想这样一个例子:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
NSMutableString *mutableName = [NSMutableString stringWithFormat:@"hello"];
p.name = mutableName;
[mutableName appendString:@" world"];
复制代码
因为Person.name
使用的strong修饰,它对于赋值对象进行的浅拷贝,那么Person.name
此时实际指向与mutableName
指向的同一块的内存区,若是将mutableName
的内容修改,此时Person.name
也会修改,这并非咱们想要的,因此咱们使用copy来修饰,这样即便赋值对象是一个可变对象,也会在setter方法中copy一份不可变对象再赋值。 而对于可变对象的属性来讲,若是使用copy修饰,从上面可知会获得一个不可变对象再赋值,那么若是你想要修改对象内容的时候,就会抛出异常,因此咱们用strong。
assgin用于基础类型,能够修饰对象,可是这个对象在销毁后,这个指针并不会置空,会形成野指针错误。 weak用于对象,没法修饰基础类型,而且在对象销毁后,指针会自动置为nil,不会引发野指针崩溃。
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫作“自动合成”(autosynthesis)。须要强调的是,这个过程由编译器在编译期执行,因此编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 以外,编译器还要自动向类中添加适当类型的实例变量,而且在属性名前面加下划线,以此做为实例变量的名字。也能够在类的实现代码里经过 @synthesize 语法来指定实例变量的名字。
在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,咱们使用属性的目的,是但愿遵照我协议的对象能实现该属性。 category 使用 @property 也是只会生成 setter 和 getter 方法的声明,若是咱们真的须要给 category 增长属性的实现,须要借助于Runtime的关联对象。
深拷贝是对内容的拷贝,即复制一份原来的内容放在其余内存下,新对象指针指向该内存区域,与原来的对象没有关系。
浅拷贝是对指针的拷贝,即建立一个新指针也指向原对象的内存空间,至关于给原来对象索引计数+1。
对于可变对象来讲,Copy和MutableCopy都是深拷贝; 对于不可变对象来讲,Copy是浅拷贝,MutableCopy是深拷贝; Copy返回的都是不可变对象,MutableCopy返回的都是可变对象。
NSArray *arr1 = @[];
NSArray *arr2 = arr1;
NSArray *arr3 = [arr1 copy];
NSArray *arr4 = [arr1 mutableCopy];
NSMutableArray *arr5 = [NSMutableArray array];
NSMutableArray *arr6 = arr5;
NSMutableArray *arr7 = [arr5 copy];
NSMutableArray *arr8 = [arr5 mutableCopy];
NSLog(@"%p %x", arr1, &arr1);
NSLog(@"%p %x", arr2, &arr2);
NSLog(@"%p %x", arr3, &arr3);
NSLog(@"%p %x", arr4, &arr4);
NSLog(@"%p %x", arr5, &arr5);
NSLog(@"%p %x", arr6, &arr6);
NSLog(@"%p %x", arr7, &arr7);
NSLog(@"%p %x", arr8, &arr8);
复制代码
打印结果
0x604000001970 e7ba22d8
0x604000001970 e7ba22c8
0x604000001970 e7ba22c0
0x604000458360 e7ba22b8
0x6040004581e0 e7ba22b0
0x6040004581e0 e7ba22a8
0x604000001970 e7ba22a0
0x604000458270 e7ba2298
复制代码
在Runtime中,为了管理全部对象的引用计数和weak指针,建立了一个全局的SideTables,实际是一个hash表,里面都是SideTable的结构体,而且以对象的内存地址做为key,SideTable部分定义以下
struct SideTable {
//保证原子操做的自旋锁
spinlock_t slock;
//保存引用计数的hash表
RefcountMap refcnts;
//用于维护weak指针的结构体
weak_table_t weak_table;
....
};
复制代码
其中用来维护weak指针的结构体weak_table_t
是一个全局表,其定义以下
struct weak_table_t {
//保存全部弱引用表的入口,包含全部对象的弱引用表
weak_entry_t *weak_entries;
//存储空间
size_t num_entries;
//参与判断引用计数辅助量
uintptr_t mask;
//hash key 最大偏移值
uintptr_t max_hash_displacement;
};
复制代码
其中全部的weak指针正是存在weak_entry_t
中,其部分定义以下
struct weak_entry_t {
//被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等。
DisguisedPtr<objc_object> referent;
union {
struct {
//可变数组,里面保存着全部指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的全部指针都会被设置成nil。
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
...
};
复制代码
weak因为不增长引用计数,因此不能在SideTable中与引用计数表放在一块儿,Runtime单独使用了一个全局hash表weak_table_t
来管理weak,其中底层结构体weak_entry_t
以weak指向的对象内存地址为key,value是一个存储该对象全部weak指针的数组。当这个对象dealloc时,假设该对象的内存地址为a,查出对应的SideTable,搜索key为a对应的指针数组,而且遍历数组将全部weak对象置为nil,并清除记录。
//建立weak对象
id __weak obj1 = obj;
//Runtime会调用以下方法初始化
id objc_initWeak(id *location, id newObj)
{
//若是对象实例为nil,当前weak对象直接置空
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
//更新指针指向
static id storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//查询当前weak指针原指向的oldSideTable与当前newObj的newSideTable
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
.....
//解除weak指针在旧对象中注册
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
//添加weak到新对象的注册
if (haveNew) {
newObj = (objc_object *)
//这个地方仍然须要newObj来核对内存地址来找到weak_entry_t,从而删除
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
} else {}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
复制代码
分类用于对已有的类添加新方法,并不须要建立新的子类,不须要访问原有类的源代码,能够将类定义模块化地分布到多个分类中。
分类的实现是基于Runtime动态的将分类中方法添加到类中,Runtime中经过class_addIvar()
方法添加成员变量,但苹果对该方法只能在构造一个类的过程当中调用,不容许对一个已存在的类动态添加成员变量。 为何苹果不容许?这是由于对象在运行期已经给成员变量都分配了内存,若是动态的添加属性,不只须要破坏内部布局,并且已经建立的类的实例也不符合当前类的定义,这简直是灾难性的。可是方法保存在类的可变区域中,修改是不会影响类的内存布局的,因此没问题。
在分类中声明属性能够编译经过,可是使用该属性,会报找不到getter/setter方法,这是因为即便声明属性,也不回生成_成员变量,天然也没有必要实现getter/setter方法,那么咱们就须要经过Runtime的关联对象来为属性实现getter/setter方法。 例如对Person的一个分类增长SpecialName
属性,实现代码以下
#import "Person+Test.h"
#import <objc/runtime.h>
// 定义关联的key
static const char* specialNameKey = "specialName";
@implementation Person (Test)
- (void)setSpecialName:(NSString *)specialName {
// 第一个参数:给哪一个对象添加关联
// 第二个参数:关联的key,经过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, specialNameKey, specialName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)specialName {
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, specialNameKey);
}
@end
复制代码
其中关联对象也是存在一个hash表中,经过内存寻址,当对象销毁时,会找到对应存储的关联对象作清理工做。
扩展与分类类似,至关于匿名分类,但分类一般有.h和.m文件,而扩展经常使用于临时对某个类的接口进行扩展,通常声明私有属性、私有方法、私有成员变量。
block是对C语言的扩展,用来实现匿名函数的特性。
block对应的结构体以下
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
//全部对象都有该指针,用于实现对象相关的功能
void *isa;
//用于按 bit 位表示一些 block 的附加信息
int flags;
//保留变量
int reserved;
//函数指针,指向具体的 block 实现的函数调用地址
void (*invoke)(void *, ...);
//表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针
struct Block_descriptor *descriptor;
/* Imported variables. */
//捕获的变量,block 可以访问它外部的局部变量,就是由于将这些变量(或变量的地址)复制到告终构体中
};
复制代码
在 Objective-C 语言中,一共有 3 种类型的 block: _NSConcreteGlobalBlock
全局的静态 block,不会访问任何外部变量。 _NSConcreteStackBlock
保存在栈中的 block,当函数返回时会被销毁。 _NSConcreteMallocBlock
保存在堆中的 block,当引用计数为 0 时会被销毁。
事件响应,数据传递,链式语法。
这是由于block会从新生成一份变量,因此局部变量修改不会影响block中的变量,并且编译器加了限制,block中的变量也不容许修改。
全局变量所占用的内存只有一份,供全部函数共同调用,block能够直接使用,而不须要深拷贝或者使用变量指针。 静态变量实际与__block修饰相似,block是直接使用的指向该静态变量的指针,并未从新深拷贝。
将局部变量使用__block修饰,告诉编译器这个局部变量是能够修改的,那么block不会再生成一份,而是复制使用该局部变量的指针。
咱们为了防止循环引用使用了weakSelf
,可是某些状况在block的执行过程当中,会出现self忽然释放的状况,致使运行不正确,因此咱们使用strongSelf
来增长强引用,保证后续代码均可以正常运行。 那么岂不是会致使循环引用?确实会,可是只是在block代码块的做用域里,一旦执行结束,strongSelf就会释放,这个临时的循环引用就会自动打破。
MRC下使用copy,ARC下均可以。 MRC下block建立时,若是block中使用了成员变量,其类型是_NSConcreteStackBlock
,它的内存是放在栈区,做用域仅仅是在初始化的区域内,一旦外部使用,就可能形成崩溃,因此通常使用copy
来将block拷贝到堆内存,此时类型为_NSConcreteMallocBlock
,使得block能够在声明域外使用。 ARC下只有_NSConcreteGlobalBlock
和_NSConcreteMallocBlock
类型,若是block中使用了成员变量,其类型是_NSConcreteMallocBlock
,因此不管是strong
仍是copy
均可以。
虽然编译器作了限制,可是咱们仍然能够在block中经过指针修改,如
int a = 1;
void (^test)() = ^ {
//经过使用指针绕过了编译器限制,可是因为block中是外面局部变量的拷贝,因此即便修改了,外面局部变量也不会变,实际做用不大。
int *p = &a;
*p = 2;
NSLog(@"%d", a);
};
test();
NSLog(@"%d", a);
复制代码
全部对象在runtime层都是以struct展现的,NSObject就是一个包含了isa指针的结构体,以下
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
复制代码
而Class也是个包含了isa的结构体,以下
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;
复制代码
objc_object
中的isa指针告诉了runtime本身指向了什么类,objc_class
中的isa指向父类,最终根元类的isa会指向本身,造成闭环。 Objective-C 2.0中并未具体暴露实现,但咱们能够看到 Objective-C 2.0中的大概实现,包含了父类,成员变量表,方法表等。
ARC(Automatic Reference Counting)自动引用计数,是苹果在WWDC 2011大会上提出的内存管理技术,常应用于iOS和MacOS。 GC(Garbage Collection)垃圾回收机制,因为Java的流行而广为人知,简单说就是系统按期查找不用的对象,并释放其内存。
不是,虽然大部分使用ARC的内存管理都作得很好,可是若是使用不当,仍然会形成内存泄漏,例如循环引用;OC与Core Foundation类进行桥接的时候,管理不当也会内存泄漏;指针未清空,形成野指针等。
分为五个区:栈区,堆区,全局区,常量区,代码区。程序启动后,全局区,常量区和代码区是已经固定的,不会再更改。
存一些局部变量,函数跳转地址,现场保护等,该区由系统处理,无需咱们干预。大量的局部变量,深递归,函数循环调用均可能耗尽栈内存而形成程序崩溃 。
即运行时内存,咱们建立对象就是在这里,须要开发者来管理。
用于存放全局变量和静态变量,初始化的放在一块区域,未初始化的放在相邻的一块区域。
存放常量,如字符串常量,const常量。
存放代码。
static修饰的变量存储在静态区,在编译时就分配好了内存,会一直存在app内存中直到中止运行。该静态变量只会初始化一次,在内存中只有一份,而且限制了它只能在声明的做用域中使用,例如单例。 注:static也能够在.h文件中声明,可是因为头文件能够被其余文件任意引入使用,此时限制做用域没有任何意义,违背了它的初衷,并且重复声明也会报错。
const用于声明常量,只读不可写,该常量存储在常量区,编译时就分配了相关内存,也会一直存在app内存直到中止运行,示例代码以下:
int const *p // *p只读 ;p变量
int * const p // *p变量 ; p只读
const int * const p //p和*p都只读
int const * const p //p和*p都只读
复制代码
extern用于声明外部全局变量/常量,告诉编译器须要找对应的全局变量,须要在.m中实现,以下写法是错误的
//Person.h
extern NSString *const Test = @"test";
复制代码
正确的使用方法是
//Person.h
extern NSString *const Test;
//Person.m
NSString *const Test = @"test";
复制代码
它经常使用于让当前类可使用其余类的全局变量/常量,也常常用于统一管理全局变量/常量,更规范整洁,而且在打包时配合const使用,能够避免其余人修改。 extern能够在多处声明,可是实现只能是一份,不然会报重复定义。
预处理是C语言的一部分,在编译以前,编译器会对这些预处理命令进行处理,这些预处理的结果与源程序一块儿编译。
@class
仅仅是告诉编译器有这个类,至于类里有什么信息,这里不须要知道,没法使用该类的实例变量,属性和方法。其编译效率较#import
更高,由于#import
须要把引用类的全部头文件都走一遍,而@class
不用。 #import
还会形成递归引用,若是A、B两类只相互引用,不会报错,可是若是任意一方声明了对方的实例,就会报错。
因为OC中并不能隐藏系统方法,例如咱们在实现单例时,为了不其余人对单例类new、alloc、copy及mutableCopy,保证整个系统中只有一个单例实例,咱们能够在头文件中声明不可用的方法,以下:
//更简洁
+(instancetype) alloc NS_UNAVAILABLE;
+(instancetype) new NS_UNAVAILABLE;
-(instancetype) copy NS_UNAVAILABLE;
-(instancetype) mutableCopy NS_UNAVAILABLE;
//能自定义提示语
+(instancetype) alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
+(instancetype) new __attribute__((unavailable("call sharedInstance instead")));
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
复制代码
如何使用? 安装CocoaPods环境后,cd到须要的工程根目录下,经过pod init
建立Podfile文件,打开该文件,添加须要的三方库pod 'xxx'
,保存关闭,输入pod install
便可安装成功。 若是成功后import不到三方库的头文件,能够在User header search paths中添加$(SRCROOT)而且选择recursive。
原理 将全部的依赖库都放到另外一个名为Pods的项目中,而后让主项目依赖Pods项目,这样源码管理工做就从主项目移到了Pods项目。这个Pods项目最终会变成一个libPods.a的文件,主项目只须要依赖这个.a文件便可。
NULL是C语言的用法,此时调用函数或者访问成员变量,会报错。能够用来赋值基本数据类型来表示空。 nil和Nil是OC语法,调用函数或者访问成员变量不会报错,nil是对object对象置为空,Nil是对Class类型的指针置空。 NSNull是一个类,因为nil比较特殊,在Array和Dictionary中被用于标记结束,因此不能存放nil,咱们能够经过NSNull来表示该数据为空。可是向NSNull发送消息会报错。
NSDictionary(字典)是使用哈希表 Hash table(也叫散列表)来实现的。哈希表是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它经过计算一个关于键(key)值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称作散列函数,存放记录的数组称作哈希表。也就是说哈希表的本质是一个数组,数组中每个元素其实就是NSDictionary键值对。
库是共享程序代码的方式,通常分为静态库和动态库。
静态库:连接时完整地拷贝至可执行文件中,被屡次使用就有多份冗余拷贝。文件后缀通常为.a,开发者本身创建的.framework是静态库。 动态库:连接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。文件后缀通常为.dylib,系统的. framework就是动态库。
.a是纯二进制文件,还须要.h文件以及资源文件,而.framework能够直接使用。
@synthesize语义是若是你没有手动实现setter/getter方法,那么编译器会自动加上这两个方法。能够用来改变实例变量的名称,如@synthesize firstName = _myFirstName;
。
@dynamic是告诉编译器不须要它自动生成,由用户本身生成(固然对于 readonly 的属性只需提供 getter 便可)。假如一个属性被声明为 @dynamic var,而后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,可是当程序运行到 instance.var = someVar,因为缺 setter 方法会致使程序崩溃;或者当运行到 someVar = var 时,因为缺 getter 方法一样会致使崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
咱们要搞清楚一个问题,什么状况下不会autosynthesis(自动合成)?
访问了野指针,好比对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。