2017-12-07 18:07:09 chenglei9128 阅读数 182

只在此总结一些项目中容易忽略的点,可能后期会补充,因为有些块我暂时也没想到怎么总结,所以暂时只有这些。
- 在类的头文件(.h文件)尽量使用@class(向前声明),在类的实现文件(.m文件)中引入需要的头文件,既可以解决相互引用发生的错误,又可以减少编译的时间。
- 多用字面量语法,少用与之等价方法,如NSArray = @[@"hello", @"world"];这种语法糖可以更直观的了解其数组或者字典元素,但是一定要注意,确保每个元素都有值,不能为nil,否则会抛出异常。
- 多用类型常量,少用#define预处理指令,因为#define会增加内存,并且无法定义数据类型。考虑到多人开发,可以创建一个类来统一管理常用的类型常量。推荐苹果官方定义这种UIKIT_EXTERN NSString *const valueName; 如果该类型常量仅在对应的编译单元内使用,则可使用static const关键字来修饰,直接定义在当前的编译单元内。
- 用枚举表示状态、选项,当状态或者选项固定时采用switch判断其条件时可以不写default,这样做的好处是当所传参数不在枚举的类型之中时编译器会报错,当然这就要求开发者编写代码时要对代码执行有充分的认识。
- 用前缀避免命名空间冲突,建议前缀用大于等于三个的字母,因为两个字母的专利已经被苹果所占有了,说不定哪天苹果就搞一个两个字母的系统前缀。
- 提供全能初始化方法,尽量减少外部暴露的变量为可编辑的。防止外部意外修改类的属性。
- 为私有方法加上前缀,比如p_privateryAction
- 当代理次数调用频繁时,可以定义一个BOOL值,然后在setDelegate:方法中通过responsToSelector方法设置这个BOOL值,从而在下次回调代理方法的时候只需要判断这个值即可,而不需要每次都通过responsToSelector判断是否设置了代理并实现了代理方法。
- 以“自动释放池快”(autorelease)来降低内存峰值。最简单的就是在for循环中,当for循环中需要不断创建临时对象时,这个时候,因为当前的线程还没有执行下一次事件,所以此时临时对象会保存在内存当中不会释放。所以可以采用下面这种方式:

    for (int i = 0; i < 10000; i++) {
        @autoreleasepool {
            Person *person = [[Person alloc] init];
            [_mutableArr addObject:person];
        }
    }
2016-02-22 16:27:52 liyunliyun08 阅读数 640

阅读次数的笔记,纯粹是每个方法之后的要点合集


第1条 了解Objective-C语言的起源
1.Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一条消息之后,究竟应执行何种代码,由运行期而非编译器来决定。
2.理解C语言的核心概念有助于写好Objective-C程序。尤其要掌握内存模型与指针。

第2条 在类的头文件中尽量少引入其他头文件
1.除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
2.有时无法使用向前声明,比如要声明某个类遵循一项协议。在这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-contunuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

第3条 多用字面量语法,少用与之等价的方法
1.应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
2.应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
3.用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值里不含nil。

第4条 多用类型常量,少用#define预处理指令
1.不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
2.在实现文件中使用static const来定义“只在编译单元内可见的常量”。由于此类常量不在全局符号表中,所以无须为其名称加前缀。
3.在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号表中,所以其名称应加以区分,通常用与之相关的类名做前缀。

第5条 用枚举表示状态、选项、状态码
1.应该用枚举来表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
2.如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位与操作将其组合起来。
3.用NS_ENUM与NS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。(需要相护组合的使用NS_OPTIONS,不要相互组合的使用NS_ENUM)
4.在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器会提示开发者未处理新的枚举类型

第6条 理解“属性”这一概念
1.可以用@property语法来定义对象中所封装的数据。
2.通过“特质”来指定存储数据所需的正确语义。
3.在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。
4.开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能。

第7条 在对象内部尽量直接访问实例变量
1.在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
2.在初始化方法及dealloc方法中,总是应该直接通过实例变量来读写数据。
3.有时会使用惰性初始化技术配置某粉数据,这种情况下,需要通过属性来读取数据。

第8条 理解“对象等同性”这一概念
1.若想监测对象的等同性,请提供“isEqual:”与hash方法。
2.相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
3.不要盲目地逐个监测每条属性,而是应该依照具体需求来制定监测方案。
4.编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

第9条 理解“对象等同性”这一概念
1.类族模式可以把实现细节隐藏在一套简单的公共接口后面。
2.系统框架中经常使用类族。
3.从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。

第10条 在既有类中使用关联对象存放自定义数据
1.可以通过“关联对象”机制来把两个对象连起来。
2.定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”。
3.只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。

第11条 理解objc_msgSend的作用
1.消息由接收者、选择子及参数构成。给某对象“发送消息”也就相当于在该对象上“调用方法”。
2.发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查处对应的方法,并执行其代码。

第12条 理解消息转发机制
1.若对象无法响应某个选择子,则进入消息转发流程。
2.通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
3.对象可以把其无法解读的某些选择子转交给其他对象来处理。
4.经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。

第13条 用“方法调配技术”调适“黑盒方法”
1.在运行期,可以向类中新增或替换选择子所对应的方法实现。
2.使用另一份实习来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
3.一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。

第14条 理解“类对象”的用意
1.每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
2.如果对象类型无法在编译器确定,那么就应该使用类型信息查询方法来探知。
3.尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

第15条 用前缀避免命名空间冲突
1.选择与你的公司、应用程序或二者皆有关联之名称座位类名的前缀,并在所有代码中均使用这一前缀。
2.若自己所开发的程序库中用到了第三方库,则应为其中的名词加上前缀。

第16条 提供“全能初始化方法”
1.在类中提供一个全能初始化方法,并于文档里指明。其他初始化方法均应调用次方法。
2.若全能初始化方法与超类不同,则需覆写超类中的对应方法。
3.如果超类的初始化方法不适用于自类,那么应该覆写这个超类方法,并在其中抛出异常。

第17条 实现description方法
1.实现description方法返回一个有意义的字符串,用以描述该实例。
2.若想在调试时打印出更详尽的对象描述信息,则应实行debugDescription方法。

第18条 尽量使用不可变对象
1.尽量创建不可变对象。
2.若某属性仅可于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite属性。
3.不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。

第19条 使用清晰而协调的命名方式
1.起名时应遵从标准的Objective-C命名规范,这样创建出来的接口更容易为开发者所理解。
2.方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好。
3.方法名里不要使用缩略后的类型名称。
4.给方法起名时的第一要务就是确保其风格与你自己的代码或所要集成的框架相符。

第20条 为私有方法名加前缀
1.给私有方法的名称加上前缀,这样可以很容易地将其同公共方法区分开。
2.不要单用一个下划线做私有方法的前缀,因为这种做法是预留给苹果公司用的。

第21条 理解Objective-C错误模型
1.只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。
2.在错误不那么严重的情况下,可以指派“委托办法”(delegate method)来处理错误,也可以把错误信息放在NSError对象里,经由“输出参数”返回给调用者。

第22条 理解NSCopying协议
1.若想令自己所写的对象具有拷贝功能,则需要实行NSCopying协议。
2.如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。
3.复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
4.如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。

第23条 通过委托与数据源协议进行对象间通信
1.委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象。
2.将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法。
3.当某对象需要从另外一个对象中获取数据时,可以使用委托模式。这种情境下,该模式亦称“数据源协议”。
4.若有必要,可实现含有位段的结构体,将委托对象是否能相应相关协议方法这一信息缓存至其中。

第24条 将类的实现代码分散到便于管理的数个分类之中
1.使用粉类机制把类的实现代码划分成易于管理的小块。
2.将应该视为“私有”的方法归入名叫Private的分类中,以隐藏实现细节。

第25条 总是为第三方类的分类名称加前缀
1.向第三方类中添加分类时,总应给其名称加上你专用的前缀。
2.向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀。

第26条 勿在分类中声明属性
1.把封装数据所用的全部属性都定义在主接口里。
2.在“class-continuation分类”之外的其它分类中,可以定义存取方法,但尽量不要定义属性

第27条 使用“class-continuation分类”隐藏实现细节
1.通过“class-continuation分类”向类中新增实例变量。
2.如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改此属性,那么就在“class-continuation分类”中将其扩展为“可读写”。
3.把私有方法的原型声明在“class-continuation分类”里面。
4.若想使类所遵循的协议不为人所知,则可于“class-continuation分类”中声明。

第28条 通过协议提供匿名对象
1.协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所应实现的方法。
2.使用匿名对象来隐藏类型名称(或类名)。
3.如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可使用匿名对象来表示。

第29条 理解引用计数
1.引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁了。
2.在对象生命期中,其余对象通过引用来保留或释放此对象。保留与释放操作分别会递增及递减保留计数。

第30条 以ARC简化引用计数
1.有ARC之后,程序员就无须担心内存管理问题了。使用ARC来编程,可省去类中的许多“样板代码”。
2.ARC管理对象生命期的办法基本上就是:在合适的地方插入“保留”及“释放”操作。在ARC环境下,变量的内存管理语义可以通过修饰符指明,而原来则需要手工执行“保留”及“释放”操作。
3.由方法所返回的对象,其内存管理语义总是通过方法名来体现。ARC将此确定为开发者必须遵守的规则。
4.ARC只负责管理Objective-C对象的内存。尤其要注意:CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease。

第31条 在dealloc方法中只释放引用并解除监听
1.在dealloc方法里,应该做的事情就是释放指向其它对象的引用,并取消原来订阅的“键值观测”(KVO)或NSNotificationCenter等通知,不要做其它事情。
2.如果对象持有文件描述符等系统资源,那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后必须调用close方法。
3.执行异步任务的方法不应在dealloc里调用;只能在正常状态下执行的那些方法也不应在dealloc里调用,因为此时对象已处于正在回收的状态了。

第32条 编写“异常安全代码”时留意内存管理问题
1.捕获异常时,一定要注意将try块哪所创立的对象清理干净。
2.在默认情况下,ARC不生成安全处理异常所需的清理代码。开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。

第33条 以弱引用避免保留环
1.将某些引用设为weak,可避免出现“保留环”。
2.weak引用可以自动清空,也可以不自动清空。自动清空是随着ARC而引入的新特性,由运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。

第34条 以“自动释放池块”降低内存峰值
1.自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
2.合理运用自动释放池,可降低应用程序的内存峰值。
3.@autoreleasepool这种新式写法能创建出更为轻便的自动释放池。

第35条 用“僵尸对象”调试内存管理问题
1.系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
2.系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子,响应方式为:打印一条包含消息内容及其接收者的消息,然后终止应用程序。

第36条 不要使用retainCount
1.对象的保留计数看似有用,实则不然,因为任何给定时间点上的“绝对保留计数”都无法反映对象生命期的全貌。
2.引入ARC之后,retainCount方法就正式废止了,在ARC下调用该方法会导致编译器报错。

第37条 理解“块”这一概念
1.块是C、C++、Objective-C中的词法闭包。
2.块可接受参数,也可返回值。
3.块可以分配在栈或堆上,也可以是全局的。分配在栈上的块可拷贝到堆里,这样的话,就和标准的Objective-C对象一样,具备引用计数了。

第38条 为常用的块类型创建typedef
1.以typedef重新定义块类型,可令块变量用起来更加简单。
2.定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突。
3.不妨为同一个块签名定义多个类型别名。如果要重构的代码使用了块类型的某个别名,那么只需修改相应typedef中的块签名即可,无须改动其他typedef。

第39条 用handler块降低代码分散程度
1.在创建对象时,可以使用内联handler块将相关业务逻辑一并声明。
2.在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若该用handler块来实现,则可直接讲块与相关对象放在一起。
3.设计API时如果用到了handler块,那么可以增加一个参数,使调用者通过此参数来决定应该把块安排在哪个队列上执行。

第40条 用块引用其所属对象时不要出现保留环
1.如果块所捕获的对象直接或间接地保留了块本身,那么就得当心保留环问题。
2.一定要找个适当的时机解除保留环,而不能把责任推给API的调用者。

第41条 多用派发队列,少用同步锁
1.派发队列可用来表述同步语义,这种做法要比使用@synchronized块或NSLock对象更简单。
2.将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。
3.使用同步队列及栅栏块,可以令同步行为更加高效。

第42条 多用GCD,少用performSelector系列方法
1.performSelector系列方法在内存管理方法容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。
2.performSelector系列方法所能处理的选择子太过局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。
3.如果想把任务放在另一个线程上执行,那么最好不要用performSelector系列方法,而是把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。

第43条 掌握GCD及操作队列的使用时机
1.在解决多线程与任务管理问题时,派发队列并非唯一方案。
2.操作队列提供了一套高层的Objective-C API,能实现纯GCD所具备的绝大部份功能,而且还能完成一些更为复杂的操作,那些操作若改用GCD来实现,则需另外编写代码。

第44条 通过Dispatch Group机制,根据系统资源状况来执行任务
1.一系列任务可归入一个dispatch group 之中。开发者可以在这组任务执行完毕时获得通知。
2.通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需编写大量代码。

第45条 使用dispatch_once来执行只需运行一次的线程安全代码
1.经常需要编写“只需执行一次的线程安全代码”。通过GCD所提供的dispatch_once函数,很容易就能实现此功能。
2.标记应该声明在static或global作用域中,这样的话,在把只需执行一次的块传给dispatch_once函数时,传进去的标记也是相同的。

第46条 不要使用dispatch_get_current_queue
1.dispatch_get_current_queue函数的行为常常与开发者所预期的不同。此函数已经废弃,只应做调试之用。
2.由于派发队列是按层级来组织的,所以无法单用某个队列对象来描述“当前队列”这一概念。
3.dispatch_get_current_queue函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能改用“队列特定数据”来解决。

第47条 熟悉系统框架
1.许多系统框架都可以直接使用。其中最重要的是Foundation与CoreFoundation,这两个框架提供了构建应用程序所需的许多核心功能。
2.很多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等。
3.请记住:用纯C写成的框架与用Objective-C写成的一样重要,若想成为优秀的Objective-C开发者,应该掌握C语言的核心概念。

第48条 多用块枚举,少用for循环
1.遍历collection由四种方式。最基本的办法是for循环,其次是NSEnumerator遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”。
2.“块枚举法”本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。
3.若提前知道待遍历的collection含有何种对象,则应修改块签名,指出对象的具体类型。

第49条 对自定义其内存管理语义的collection使用无缝桥接
1.通过无缝桥接技术,可以在Foundation框架中的Objective-C对象与CoreFoundation框架中的C语言数据结构之间来回转换。
2.在CoreFoundation层面创建collection时,可以指定许多回调函数,这些函数表示此collection应如何处理其元素。然后,可运用无缝桥接技术,将其转换成具备特殊内存管理语义的Objective-C collection。

第50条 构建缓存时选用NSCache而非NSDictionary
1.实现缓存时应选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且是“线程安全的”,此外,它与字典不同,并不会拷贝键。
2.可以给NSCache对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”,它们仅对NSCache起指导作用。
3.将NSPurgeableData与NSCache搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData对象所占内存为系统所丢弃时,该对象自身也会从缓存中移除。
4.如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。

第51条 精简initialize与load的实现代码
1.在加载阶段,如果实现了load方法,那么系统就会调用它。分类里也可以定义此方法,类的load方法要比分类中的先调用。与其他方法不同,load方法不参与覆写机制。
2.首次使用某个类之前,系统会向其发送initialize消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。
3.load与initialize方法都应该实现得精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率。
4.无法在编译器设定的全局常量,可以放在initialize方法里初始化。

第52条 别忘了NSTimer会保留目标对象
1.NSTimer对象会保留其目标,直到计时器本身失效为止,调用invalidate方法可令计时器失效,另外,一次性的计时器在触发完任务之后也会失效。
2.反复执行任务的计时器,很容易引入保留环,如果这种计时器的目标对象又保留了计时器本身,那肯定会导致保留环。这种环状保留关系,可能是直接发生的,也可能是通过对象图里的其他对象间接发生的。
3.可以扩充NSTimer的功能,用“块”来打破保留环。不过,除非NSTimer将来在公共接口里提供此功能,否则必须创建分类,将相关实现代码加入其中。




2017-04-20 15:55:00 u012860928 阅读数 122

一、Category 的作用
1、可以在不修改原类的基础上,为这个类扩充一些方法
2、一个庞大的类可以分模块开发,由多个人来编写,有利于团队合作

二、Category的格式

// 声明
@interface ClassName(CategoryName)
NewMethod; // 添加方法,不允许添加变量
@end

// 实现
@implementation ClassName(Category)
NewMethod
@end

三、Category文件
OC文件,选择要扩充的类,filename一般写自己的名字,Xcode自动生成“类名+名字”的.h和.m文件

四、Category注意事项
1、分类用于给原有类添加方法,只能添加方法,不能添加属性(成员变量)
2、可以在分类中访问原有类中.h中的属性
3、分类中与原类中有同名方法,会执行分类中的方法

2018-07-18 09:44:44 fengzhixinfei 阅读数 63

15、使用前缀避免命名空间冲突

1、重命名符号错误

OC没有其他语言内置的命名空间(namespace),命名时要避免潜在的命名冲冲突(naming clash):

比如如下错误,就是重命名符号错误(duplicate symbol error)。

duplicate symbol _OBJC_CLASS_$_DogObject in:
    xxx/DogObject-ED8631F460AAA56A.o
    xxx/DogObject-917EE703FAC7406E.o
duplicate symbol _OBJC_METACLASS_$_DogObject in:
    xxx/x86_64/DogObject-ED8631F460AAA56A.o
    xxx/DogObject-917EE703FAC7406E.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
  • 解决方法:把错误中提到的duplicate symbol _OBJC_CLASS_$_DogObject类名检查一遍,重新配置。

  • 避免方法(尤其是在引入很多三方库,或者项目工程文件较多的时候,应该说在所有的项目中都要如此):变相实现命名空间–为所有名称都加上是当前缀。

另,苹果宣称保留使用所有两个字母的前缀(two-letter Prefix)的权利。所以自己选用前缀最好是三个字母。

2、给新增分类和分类方法加上前缀(第25条)

分类机制通常用于向无源码的既有类中新增功能。

分类的方法是直接加到类中的,就好比是类中固有的方法,将分类方法加入类中这一操作时在运行期系统加载分类时完成的。运行期系统会把分类中所实现的每个方法都加入类的方法列表中。

如果类中本来就有这个方法,分类中又实现了一次,那么分类中方法会覆盖原来的实现代码。有可能会发生多次覆盖。

如果多个分类名称相同,在运行期,是不会报错的。但是加载的分类有可能不是你所期望的。

如果有相同的方法,那么运行时调用的不一定是你想要的方法。

运行期不会报错,但是在实现结果的时候,就会出现未知错误。

比如实现了NSString的两个分类,同时都有一个分类方法”- (NSString *)urlEncordedString;”那么在调用过程中,就不知道是调用哪个分类中实现的方法。同样能够正常编译通过。

NSString *urlString = @"http://www.baidu.com";
NSLog(@"%@",[urlString urlEncordedString]);

因为不会报错,这种问题比较难发现,所以在写之前就避免这种情况就显得非常重要。

当然如果分类名相同,但是方法名不同时,有可能出现的问题是:No visible @interface for 'NSString' declares the selector 'seconString',你无法调用自己实现的方法。系统只是提供最后加载到的分类,如此而已。

  • 如何避免:添加分类时,给分类名称加上专用前缀,同时给分类方法名加上专用前缀。

3、类的实现文件中所用的纯C函数及全局变量

在类的实现文件中所用的纯C函数及全局变量,在编译好的目标文件中,这些名称要算作“顶级符号”(top-level symbol)。

如果在不同的类文件中实现同样的C函数,就会报重命名符号错误(duplicate symbol error)

duplicate symbol _completion in:
    xxx/ViewController.o
    xxx/NSString+Http.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

duplicate symbol _completion会指出错误方法名completion,另外会在下面的描述中指明在那些文件中出现冲突。

  • 解决方法:找到错误的类和方法名,修改。

  • 避免方法:C函数名加前缀,同时加上类名信息,在回溯查找问题是就能很快确定位置。

同样的,即使在实现文件中声明全局静态变量,在不同文件中声明相同名称的变量,也会出现名称冲突错误:

这里写图片描述

duplicate symbol _NameString in:
    xxx/ViewController.o
    xxx/NSString+Http.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
  • 解决方法:找到错误的类和声明的变量名,修改。

  • 避免方法:声明全局变量,变量名前加前缀。

4、所开发的程序库中用到第三方库,给第三方库加前缀

这个问题很简单,如果要把自己封装的程序库给别人用,同时使用了不同的第三方库。那么在别人引用的时候,如果他工程中也使用相同的第三方库,就会出现重命名符号错误。

另外考虑到所使用第三方库版本不同,那么,在封装自己的程序库时就要将所用到的第三方库中的文件添加前缀,避免此类问题。

16、提供全能初始化方法

全能初始化方法(designated initializer):为对象提供必要信息以便其能完成工作的初始化方法。

可以通过警告或者设置默认值调用全能初始化方法的方式,实现初始化。

全能初始化方法的调用链一定要维系,也即是,集成关系中,初始化方法的维护调用。

Mac OS X 的APPKit会iOS的UIKit两个UI框架都广泛运用序列化机制(serialization mechanism),将对象序列化,保存至XML格式的XIB文件中农。这些XIB文件通常用来存放视图控制器机器视图布局。加载NIB文件时,系统会在解压缩的过程中解码视图控制器。

  • 在类中提供一个全能初始化方法,并于文档里指明。其他初始化方法均应调用此方法。

  • 若全能初始化方法与超类不同,则需要覆写超类中的对应方法。

  • 若超类初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常。

17、实现description方法

实现description方法返回一个有意义的字符串,用以描述该实例。

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ %zd",_dogName,_dogAge];
}

若想在调试时(LLDB)打印出更详尽的对象描述信息,则应实现debugDescription方法。

- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p \"%@ %zd \">",[self class],self,_dogName,_dogAge];
}

打印结果

18、尽量使用不可变对象 –

关联第6条-属性

尽量减少对象中的可变内容,应该尽量把对外公布出来的属性设为只读,并且只在必要时才将属性对外公布。

如果想要修改封装在对象内部的数据,同时不将哲学数据为外人所动,可以在对象内部将readonly属性重新声明为readwrite。

在定义类的公共API时,对象里表示各种collection的那些属性究竟应该设成可变的,还是不可变的。

  • 尽量创建不可变的对象
  • 若某属性仅可于对象内部修改,则在class-continuation分类中将其由readonly扩展为readwrite。
  • 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。

19、使用清晰协调的命名方式

OC中一般采用驼峰式大小写命名法。

1)方法命名

  • 如果方法的返回值是新创建的,那么方法名的首个词应是返回值的类型,除非前面还有修饰语,如localizedString。属性的存取方法不遵循这种命名方式,一边惹味这种方法不会创建新对象,即使又是返回内部对象的一份拷贝,也认为那相当于原有的对象。这些存取方法应按照其所对应的属性来命名。
- (NSString *)stringOfDogInfomation;

- (NSDictionary *)dictionaryOfDogInfomation;
  • 应该把表示参数类型的名词放在参数前面。

  • 如果方法要在对象上执行操作,就应包含动词,若执行操作还需要参数,应在动词后面加上一个或多个名词。

  • 不要使用str这样的简称,而用string这样的全称。
[string lowercaseString];
  • Boolean属性应加is前缀,如果方法返回非属性的Boolean值,那么应该根据其功能,选用has或is前缀。

[string hasSuffix:@"this"];
[string isEqualToString:@"xxxx"];

  • get前缀留给那些借由输出参数来保存返回值的方法,

清晰明了,统一规范

2)类与协议的命名

应该为类和协议加上前缀,避免命名空间冲突。

命名方式要协调一致,如果要从其他框架中继承子类,务必遵循其命名惯例。

若要自定义委托协议,则名称中应包含委托发起方的名字,再加上Delegate

  • 起名时,遵从标准的OC命名规范
  • 方法名要言简意赅,从左只有读起来想个日常用语中的句子。
  • 方法名里不要使用缩略后的类型名称
  • 方法起名,确保风格与自己的代码或所要集成的框架相符。

20、为私有方法名加前缀

为在内部使用的私有方法加前缀,区分公共方法和私有方法,便于修改方法名和方法签名。

依据个人习惯,p_method

  • 在私有方法名称前加上前缀,区分私有和公共方法。
  • 不要单用一个下划线做私有方法的前缀,因为这种是苹果公司预留的。

21、理解OC错误模型

1)异常 exception –fatal error致命错误

OC中,在激起罕见的情况下抛出异常,异常抛出之后不再考虑恢复问题,应用程序此时应该退出。不需要再辨析复杂的“异常安全”代码。

异常一般只用于处理严重错误(fatal error 致命错误)。

@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:string userInfo:nil];

比如编写某个抽象基类,正确用法是先从中集成一个子类,再用这个子类。这种情况下,如果直接使用了这个抽象基类的,那么可以抛出异常。

OC没有办法将某个类标记为“抽象类”。要想达成效果,最好的办法是在那些子类必须覆写的超类方法里抛出异常。

2)其他错误 –nonfatal error 非致命错误

OC语言所用编程范式为:令方法返回nil/0,或是使用NSError,表明有错误发生。

NSError对象封装了三条消息:

  • Error domain(错误范围,类型为字符串)
    错误发生的范围,也就是产生错误的根源,通常用一个特有的全局变量来定义。如NSURLErrorDomain-处理URL的子系统,在从URL中解析或取得数据出错。

  • Error code 错误码,其类型为整数
    独有的错误代码,用以指明在某个范围内具体发生了何种错误。某个特定范围内可能会发生一些列相关错误,这些错误情况通常采用enum来定义。如,HTTP请求出错时,会把HTTP状态码设为错误码。

  • User info 用户信息,其类型是字典
    有关次错误的额外信息,其中或许包含一段本地化的描述(localized description),或许还包含导致该错误发生的另一个错误,经由此种信息,可将相关错误串成一条错误连(chain of errors)

NSError的用法:

  • 通过委托协议来传递错误。
    当有错误发生时,当前对象会把错误信息经由协议的某个方法传递给其委托对象(delegate)。如NSURLConnection在其委托协议NSURLConnectionDelegate中定义代理方法- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

  • 经由方法的输出参数返回给调用者。

- (BOOL)doSomething:(NSString *)thing error:(NSError **)error {
    if ([thing isEqualToString:@"1"]) {
        return YES;
    }
    *error = [NSError errorWithDomain:NSURLErrorDomain code:100 userInfo:@{@"key":@"something wrong"}];
    return NO ;
}
NSError *error;
BOOL ret  = [littleDog doSomething:@"0" error:&error];
if (!ret) {
    NSLog(@"error : %@",[error debugDescription]);
}

另外,定义自己的指定的专用错误范围字符串,使用这个字符串创建NSError对象,就能确定错误来源。

extern NSString *const ZYDErrorDomain;

typedef NS_ENUM(NSUInteger,ZYDError) {
    ZYDErrorUnknown                 = -1, //未知错误
    ZYDErrorBadInput                = 500,
};

3)

  • 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常。
  • 一般错误,可使用指派委托方法来处理错误,也可以把错误信息放在NSError对象中,经由输出参数返回给调用者。

22、理解NSCopying协议

1)不可变拷贝 NSCopying

OC 中如果需要拷贝对象,需要通过copy方法完成。如果希望自己的类支持拷贝操作,就要实现NSCopying协议。该协议只要一个方法:

- (id)copyWithZone:(NSZone *)zone;

在以前开发程序时,会据此吧内存分成不同的区(zone),而对象会创建在某个区里面。现在不用,每个程序只有一个区:默认区(default zone)。所以需要实现这个方法,但是不用担心zone参数。

若要某个类支持拷贝功能,需要改类声明遵从NSCoping协议,并实现其中的方法就可以。

.h
@interface DogObject : NSObject <NSCopying>

@end
.m

@interface DogObject()
{
    NSMutableArray *_familys;//内部成员变量,并非属性
}
@end

@implementation DogObject

- (instancetype)initWithDogName:(NSString *)dogName age:(NSInteger)age {
    self = [super init];

    if (self) {
        _dogName = [dogName copy];
        _dogAge = age;
    }
    return self;
}

#pragma mark -- NSCopying
- (id)copyWithZone:(NSZone *)zone {
    DogObject *copy = [[[self class] allocWithZone:zone] initWithDogName:_dogName age:_dogAge];
    copy -> _familys = [_familys mutableCopy]; //有
    return copy;
}

2)可变拷贝 NSMutableCopying

定义一个方法:
- (id)mutableCopyWithZone:(NSZone *)zone,与copy类似,也用默认的zone参数来调mutableCopyWithZone:。如果类分为可变版本,可不可变版本,需要实现NSMutableCopying。

3)深拷贝

深拷贝:在拷贝对象自身是,将其底层数据也一并复制过去。

浅拷贝:之拷贝容器对象本身,而不复制漆黑中的数据。

容易内的对象并不都能拷贝,而且调用者也未必要在拷贝容器的同时一并拷贝其中的每个对象。

一般NSCopying大多数情况下执行的是浅拷贝,如需要在对象上执行深拷贝,那么除非该类的文档说它是用深拷贝来实现NSCopying协议的,否则,要么寻找能够执行深拷贝的方法,要么自己编写方法来实现。

比如,NSArray中的方法:- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag如果flag为YES,该方法会向数组中每个元素发送copy信息,用拷贝好的创建新的Array,并返回给调用者。

可以给对象创建自定义深拷贝方法:

- (id)deepCopy {
    DogObject *copy = [[[self class] alloc] initWithDogName:_dogName age:_dogAge];
    copy -> _familys = [[NSMutableArray alloc] initWithArray:_familys copyItems:YES];
    return copy;
}

4)

  • 令自己所写的对象具有拷贝功能,需要实现NSCopying协议
  • 如果自定义对象分为可变和不可变版本,需要同时实现NSCopying与NSMutableCopying协议。
  • 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下尽量执行浅拷贝
  • 如果对象需要深拷贝,那么新增一个专门执行深拷贝的方法。

iOS 检测版本更新

阅读数 544

没有更多推荐了,返回首页