三十

三十
OC、类、对象、运行期
动态绑定
首先是OC其实是基于“消息机制”的
调用的时候:
Student *student = [Student new];[student getName:name];
代码会被翻译为以下执行:(还是类似函数调用,但是实际执行的时候查方法列表来执行方法)
objc_msgSend(student,@selector(getName:),name);
selector通过NSObject也就是类的isa指针找到,isa指针结构里面有方法列表。
这一切都是在运行时也就是runtime发生的,在编译的时候并不会确定你调用的是什么方法,这也是OC可以使用category来增加方法的基础。而C语言的函数调用是静态绑定的,也就是在编译时就确定了你调用的是哪个函数。

内存分配
对象所占用的内存总是在Heap堆里面的,指向对象的指针是在栈帧Stack frame里面的。
heap上面的内存需要管理,而stack里面的在没有用了以后他会自动被清除掉
所以基础变量int之类(定义时不用*的,例如CGRect)的在stack上面的是不需要weak,只要assign就可以了,因为他们的内存管理是由stack直接搞的所以不会有野指针问题。

在.h文件中减少引入其他的.h文件
当import ClassA.h的时候,如果ClassA.h里面import ClassB.h了,那么编译的时候就会一层一层的import,但有可能最后由于if else之类的逻辑分支并没有用到ClassB,那么其实就白白增加了编译的时间,所以其实ClassA.h文件里面只要声明ClassB是一个class就可以了,不用把它的.h文件引入,在.m文件里面再引入,这样当使用的时候才会真正引入。
如果Class A中import了B,而B又import A,就会造成循环引入,于是两者中有一个无法正常编译。
import有两个作用:一是和include一样,完完全全的拷贝文件的内容;二是可以自动防止文件内容的重复拷贝(即使文件被多次包含,也只拷贝一份)。
不是所有状况都可以用@class声明然后不实际引入滴,主要是在继承以及遵从某协议的情况下,需要知道具体接口有啥。delegate一般都会和自己的调用类放在一起,所以不会有这个问题。
如果在.h文件中实现某个协议,这样就必须要在.h文件中import那个协议,而且协议不能用@protocal声明但不实际import(这样编译器会报错无法找到定义),所以最好不要这么做。

尽量使用字面量
属于Foundation的NSString、NSArray、NSDictionary以及NSNumber是可以用字面量来赋值的
好处是更加简洁,并且如果有nil的话会立刻crash,防止了一些容错导致有问题无法发现。

关于static
常量需要用static & const来定义,不能只用const,因为如果不用static声明的全局变量,声明周期是到程序结束的,其他文件可以通过extern引入这个变量,作用域类似全局,当其他文件中定义了同名const会报错duplicate symbol。
而static对于局部变量的作用是将其改成声明周期到App结束,对于全局变量则是生命周期到App结束,但是只能在声明它的文件中调用,也就是作用域局限在了声明它的文件中。所以即使其他文件定义了同名static const也不会报错。
对外的const如果是对外的常量表,以及一些类似notification的名字之类的,是对外会使用的string,但是其实外面用的人而言,他们并不需要知道string实际的字面量,只要用这个变量就可以了,所以就实现声明分离而言,应该是定义在.m文件里,声明在.h文件,不要直接在.h文件里面用static const这样。

Property
property有很多修饰符,需要注意的是,声明了copy,那么同时覆写了set方法,需要在里面真的去copy传入的参数,确保声明的修饰符和实际的操作是一致。因为别人调用的时候看到声明就会认为是copy的。
写了initWithXXX的方法,而且XXX的属性也是copy修饰的,一定要在init方法里面真的copy,否则也是不一致的。
有些readonly的属性是copy的也要声明,即使不会自动给他生成set方法,但是init的时候就知道需要copy来初始化,并且外面调用的时候也会知道是copy过的,不会再次去copy一遍。

在对象内部尽量访问实例变量

用实例变量访问:
读取优点不用经过函数,直接读内存更快

用.的方式访问:

写入优点直接借用了property的修饰符,不用自己再实现一遍
写入可以触发KVO
读/写可以在setter以及getter方法里面打断点利于调试
最好是用实例变量直接读取,但赋值的时候通过.的方式。
但有两点需要特别注意一下:
(1)在init方法里面不要用self.的方式给属性赋值
因为有可能子类继承了父类,并且覆写了set属性的方法,偷偷做了检测或者抛个exception之类的,所以在init里面要直接用实例变量赋值
但如果在子类的init方法里面不能直接用父类的实例变量,就要用self.的方式赋值啦
(2)懒加载的时候要用self.的方式读取,不要直接读实例变量
因为懒加载其实就是覆写getter,看实例变量是不是nil,如果是nil就初始化实例变量,不为nil就直接返回实例变量。
如果直接读实例变量不用self.,相当于绕过了懒加载,永远都不能初始化这个实例变量

对象同等性
比较的是指针是否相等,如果想自定义比较方式,可以重写isEqual,需要注意的是,一定要考虑各种情况,例如不是同一种Class、如果父类和子类比应该返回什么等。
isEqual在单独比较的时候没有问题,比如我们姓名和生日一致的两个person对象如果用isEqual比较就是true的。但是如果把这两个equal的person加入到一个set里面,仍旧是可以同时加进去的,这就是有问题的地方了,因为同一个object是不可能作为两个object加入到一个set里面的,set应该保持唯一性,重复加入时应该无效,仍旧只有那一个object。

NSArray类族
系统框架有许多类族,大部分collection类都是类族,如NSArray和NSMutableArray。有两个抽象基类,一个用于不可变数组,一个用于可变数组。尽管具备公共接口的类有两个,但仍然可以合起来算作一个类族。不可变的类定义对所有数组都通用的方法,可变的类则定义只适用于可变数组的方法。两个类共属同一类族,这意味着两者在实现各自类型的数组时可以同用实现代码。
NSArray是个类族,其中if语句永远不可能为真。[maybeArray class]返回的类绝不可能是NSArray,因为由NSArray的初始化方法所返回的那个实例其类型是隐藏在类族公共接口后面的某个内部类型。
isMemberOfClass:判断是否是这个类的实例
isKindOfClass:判断是否是这个类或者这个类的子类的实例
子类应该继承自类族中的抽象基类。若要编写NSArray类族的子类,则需令其继承自不可变数组的基类或可变数组的基类。
子类应该定义自己的数据存储方式。开发者编写NSArray子类时,经常在这个问题上受阻。子类必须用一个实例变量来存放数组中的对象。这似乎与大家预想的不同,我们以为NSArray本身只不过是包在其他隐藏对象外面的壳,它仅仅定义了所有数组都需具备的一些接口。对于这个自定义的数组子类来说,可以用NSArray来保存其实例。
子类应当覆写超类文档中指明需要覆写的方法。
在每个抽象基类中,都有一些子类必须覆写的方法。比如说,想要编写NSArray的子类,就需要实现“count” 及 “objectAtIndex:”方法。像lastObject这种方法则无须实现,因为基类可以根据前面两个方法实现出这个方法。

在既有类中使用关联对象存放自定义数据
如果希望给一些类保存一些数据,可能会想到继承这个类,建个子类加一些属性。OC提供了一种更好一点的实现“关联对象”,可以给现有类关联数据,不用为了加属性存数据增加新的子类。
关联对象类似于,每个对象其实都有一个dictionary用于让开发者存储对象相关的数据,最开始就是空的,当你增加关联对象的时候,相当于增加了一个键值对。
于是,存取关联对象的值就相当于在NSDictionary对象上调用[object setObject:value forKey:key]与[object objectForKey:key]方法。然而两者之间有个重要差别:设置关联对象时用的键(key)是个“不透明的指针”(opaque pointer)。如果在两个键上调用“isEqual:”方法的返回值是YES,那么NSDictionary就认为二者相等;然而在设置关联对象值时,若想令两个键匹配到同一个值,则二者必须是完全相同的指针才行。

在设置关联对象值时,通常使用静态全局变量做键。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值