我只是一个搬运工,仅仅为了加深记忆,感谢做者分享,文章大部分来源:进击的蜗牛君html
alloc负责为对象的全部成员变量分配内存空间,而且为各个成员变量重置为默认值,如int型为0,BOOL型为NO,指针型变量为nil。仅仅分配空间还不够,还须要init来对对象执行初始化操做,才可使用它。若是只调用alloc不调用init,也能运行,但可能会出现未知结果。数组
所作的事情和alloc差很少,也是分配内存和初始化。安全
new只能使用默认的init初始化,而alloc可使用其余初始化方法,由于显示调用总比隐式调用好,因此每每使用alloc/init来初始化。bash
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也不相同。数据结构
具体能够分为四类:线程安全、读写权限、内存管理和指定读写方法。app
若是不写该类修饰符,默认就是atomic。二者的最大区别决定编译器生成的getter/setter方法是否属于原子操做,若是本身写了getter/setter方法,此时用什么都同样。对于atomic来讲,getter/setter增长了锁来确保数据操做的完整性,不受其余线程影响。例如线程A的getter方法运行到一半,线程B调用setter方法,name线程A仍是能获得一个完整的Value。而对nonatomic来讲,多个线程能同时访问操做,就没法保证是不是完成的Value,还会发生脏数据。可是nonatomic更快,开发中每每在可控状况下安全换效率。ide
注意:atomic并不能彻底保证线程安全,只能保证数据操做的线程安全,例如线程A使用getter方法,同时线程B、C使用setter方法,那最后线程A取到的值有3中可能:原始值、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使用@propety也是只会生成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时,查出对应的SideTable,搜索key对应的指针数组,而且遍历数组将全部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;
}
复制代码
KVC容许以字符串形式简介操做对象的属性,全称为Key Value Coding, 健值编码。
-(void) setValue:(nullable id)value forKey:(NSString *)key;
复制代码
-set<Key>:
代码经过setter方法赋值,若是-set<Key>:
没有找到,还会去查找_set<Key>:
方法。+(BOOL)accessInstanceVariableDirectly
方法,若是你重写了该方法并使其返回NO,则KVC下一步会执行setValue:forUndefineKey:
,默认抛出异常。_<key>
、 _<isKey>
、 <key>
、<isKey>
的成员变量。setValue:forUnderfinekKey
方法,默认抛出异常。- (nullable id)valueForKey:(NSString *)key;
复制代码
-get<key>
, -<key>
,is<key>
代码经过getter方法获取值。countOf<key>
,objectIn<Key>AtIndex:
和-<key>AtIndexes
方法和另外两个中的一个被找到,返回一个响应全部NSArray方法的代理集合,简单来讲就是能够当NSArray用。-countOf<key>
, -enumeratorOf<key>
和-memberOf<key>:
方法,若是三个都能找到,返回一个全部NSSet方法的代理集合,简单来讲就是能够当NSSet使用。_<key>
, _<isKey>
, <key>
, <isKey>
的成员变量,返回该成员变量的值。valueForUndefineKey:
方法,默认抛出异常。- (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)accessInstanceVariableDirectly
来决定是否搜索类似成员变量,所以只须要重写该方法并返回NO便可。
开发中可能有些须要设定对象属性不能够设定某些值,此时就须要检验Value的可用性,经过以下方法
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
复制代码
这个方法的默认实现是去探索里面是否有这样的方法-(BOOL)validate<key>:error:
,若是有这个方法,就调用这个方法来返回,没有的话就直接返回YES。注意:在KVC设值的时候,并不会主调用该方法去校验,须要开发者手动调用校验,意味着即便实现此方法,也能够赋值成功
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
复制代码
没有找到相关的Key,就会抛出NSUndefinedKeyException
异常,使用KVC时通常须要重写这两个方法。
- (void)setNilValueForKey:(NSString *)key;
复制代码
当给基础类型的对象属性设置为nil时,会抛出NSValidArgumentException
异常,通常也须要重写。
-(void)setValuesForKeysWithDictionary:
字典转model。valueForKey:
将会被传递给容器中的每个对象,而不是容器自己进行操做,由此咱们能够有效的提取容器中每一个对象的指定属性值集合。@avg
,@count
,@max
,@min
,@sum
。KVO提供了一种机制,(基于NSKeyValueObservin协议,全部的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方法,在原方法的调用先后通知观察者值的改变。而后将对象A的isa指针(isa指针告诉Runtime这个对象的类是什么)指向NSKVONotifying_A这个新类,那么对象A就变成了新建立类的实例,不只如此,Apple还重写了-class
方法来隐藏该新类,让人们觉得注册先后对象A的类并无改变,但实际上若是手动新建一个NSKVONotifying_A类,在观察者运行到注册时,便会引发重复类崩溃。
从上述实现原理来看,很明显能够知道,若是没有经过setter赋值,直接赋值该成员变量是不会触发KVO机制的,可是KVC除外,这也侧面证实了KVC和KVO是有内在联系的。
分类用于对已有的类添加新方法,并不须要建立新的子类,不须要访问原有类的源代码,能够将类定义模块化地分布到多个分类中。
分类的实现是基于Runtime动态的将分类中方法添加到类中,Runtime中经过class_addIvar()
方法添加成员变量,但苹果对该方法只能在构造一个类的过程当中用,不容许对一个已有的类动态的添加成员变量。为何苹果不容许?这是由于对象在运行期已经给成员变量都分配了内存,若是动态添加属性,不只须要破坏内部布局,并且已经建立的类的实例也符合当前类的定义,这简直是灾难性的。可是方法保存在类的可变区域中,修改是不会影响类的内存布局的因此没问题。
在分类中声明属性能够编译经过,可是使用该属性,会报找不到getter/setter方法,这是因为即便声明属性,也不会生成成员变量
,天然也没有必要实现getter/setter方法,那么咱们就须要经过Runtime的关联对象来为属性实现getter/setter方法。例如对Person的一个分类增长SPeciaName
属性,实现代码以下
#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本质也是OC对象,它内部也有isa指针,是封装了函数调用以及函数调用环境的OC对象。
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时会被销毁。
事件响应,数据传递,链式语法。
1. 为何不能直接修改局部变量?
这是由于block会从新生成一份变量,因此局部变量修改不会影响block的变量,并且编译器加了限制,block中的变量也不容许修改。
2. 为何能修改全局变量和静态变量
全局变量所占用的内存只有一份,供全部函数共同调用,block能够直接使用,而不须要深拷贝或者使用变量指针。静态变量实际与_block修饰相似,block是直接使用的指向静态变量的指针,并未从新深考贝。
3. 如何修改局部变量
将局部变量使用_block修饰,告诉编译器这个局部变量是能够修改的,那么block不会再生成一份,而是复制使用该局部变量的指针。
4. 为何要在block中使用strongWeak
咱们为了防止循环引用使用了weakSelf
, 可是某些状况在block执行过程当中,会出现self忽然释放的状况,致使运行不正确,因此咱们使用strongSelf
来增长强引用,保证后续代码能够正常运行。那么岂不是会致使循环引用? 确实会,可是只是在block代码块的做用域里,一旦执行结束,strongSelf就会释放,这个临时的循环引用就会自动打破。
5. block用copy仍是strong修饰
MRC下使用copy,ARC下均可以。MRC下block建立时,若是block中使用了成员变量,齐类型是_NSConcreteStackBlock
,它的内存是放在栈区,做用域仅仅是在初始化的区域内,一旦外部使用,就可能形成崩溃,因此通常使用copy
来将blcok拷贝到堆内存,此时类型为_NSConcreteMallocBlock
,使得block能够在声明域外使用。ARC下只有_NSConcreteGlobleBlock
和_NSConcreteMallocBlock
类型,若是block中使用了成员变量,其类型是_NSConcreteMallocBlock
,因此不管是strong
仍是copy
均可以。
6. 如何不使用_block修改局部变量?
虽然编译器作了限制,可是咱们仍然能够在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、allco、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")));
复制代码
NULL是C语言的用法,此时调用函数或者访问成员变量,会报错。能够用来赋值基本数据类型来表示空。nil和Nil是OC语法,调用函数或者访问成员变量不会报错,nil是对object对象置为空,Nil是对Class类型的指针置空。NSNull是一个类,因为nil比较特殊,在Array和Dictionary中被用于标记结束,因此不能存放nil,咱们能够经过NSNull来表示数据为空。可是向NSNull发送消息会报错。
NSDictionary(字典)是使用哈希表Hash table(也叫散列表)来实现的。哈希表是根据(key)而直接访问在内存存储位置的数据结构,也就是说,它经过计算一个关于键(key)值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称作散列函数,存放记录的数组称作哈希表。也就是说哈希表的本质是一个数组,数组中每个元素实际上是一个NSDictionary键值对。
库是共享程序代码的方式,通常分为静态库和动态库。
静态库:连接时完整地拷贝至可执行文件中,被屡次使用就有多份冗余拷贝。文件后缀通常为.a, 开发者本身创建的.framework能够直接使用。详见《iOS中.a与.framework库的区别》
@synthesize语义是若是你没有手动是实现setter/getter方法,那么编译器会自动加上这个两个方法。能够用来改变实例变量的名称,如@synthesize firstName = _myFirstName
。
@dynamic是告诉编译器不须要它自动生成,有用户本身生成(固然对于readonly的属性只提供getter便可)。假如一个属性被声明为@dynamic var ,而后你没有提供@setter方法和@getter方法,编译的时候没问题,可是当程序运行到instance.var = someVar, 因为缺setter方法会致使程序崩溃;或者当运行到sameVar = var时,因为缺乏getter方法一样会致使崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
咱们要搞清楚一个问题,什么状况下不会autosynthesis(自行合成)?
访问了野指针,好比对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。