美文网首页
系统框架

系统框架

作者: 晨阳Xia | 来源:发表于2020-11-25 19:12 被阅读0次

第47条 熟悉系统框架

什么叫做框架

将一系列代码封装为动态库(dynamic library),并在其中放入描述其接口的头文件,这样做出来的东西就叫框架。
在Mac OS X或iOS系统开发“带图形界面的应用程序”(graphical application)时,会用到名为Cocoa的框架。在iOS上称为Cocoa Touch。其实Cocoa本身不是框架,但是里面集成了一批创建应用程序时经常会用到的框架
开发者会碰到的主要框架就是Foundation,像是NSObject,NSArray,NSDictionary等类都在其中。Foundation框架中的类,使用NS这个前缀,此前缀是在Objective-C语言用作NeXTSETP操作系统的编程语言是首度确定的。Foundation框架真可谓所有Objective-C应用程序的基础。
还有个与Foundation相伴的框架,叫做CoreFoundation。虽然从技术上讲,CoreFoundation框架不是Objective-C框架,但它确实编写Objective-C应用程序时所应熟悉的重要框架。Foundation框架中的许多功能,都可以在此框架中找到对应的C语言API.

CoreFoundation

CoreFoundation与Foundation不仅名字相似,而且还有更为紧密的联系。有个功能叫“无缝桥接”(toll-free bridging)。可以把CoreFoundation中的C语言数据结构平滑转换为Foundation中的Objective-C对象,也可以反向转换。比如:Foundation框架中的字符串是NString,而它可以转换为CoreFoundation里与之等效的CFString对象。

CoreFoundation 的注意事项

由于ARC只负责Objective-C的对象,所以使用这些API时尤其需要注意内存管理问题。

UI框架

Mac OS X和iOS这两个平台的核心UI框架分别叫做AppKit及UIkit,他们都提供了构建在Foundation与CoreFoundation只上的Objective-C类。

要点

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

第48条 多用枚举少用for循环

根据定义,字典和set都是“无序的”(unordered),所以无法根据特定的证书下标来直接访问其中的值。于是,就需要先获取字典里的所有键或是set里的所有对象,这两种情况下,都可以在获取到的有序数组上遍历,以便借此访问元字典及原set中的值。创建这个附加数组会有额外开销,而且还会错创建一个数组对象,他会保留collection中的所有元素的对象。当然了,释放数组时这些附加对象也要释放,可是要调用本来不需执行的方法。

使用Objective-C的NSEnumerator来遍历

API: NSArray ,NSSet,NSDictionry对象可以直接调用以下API
- (NSENumerator *)objectEnumerator;- (NSENumerator *)keyEnumerator
`- (NSEnumerator *)reverseObjectEnumrator;

基于块的遍历

// 遍历数组
- (void)enumeratorObjectUsingBlock:(void(^)(id object, NSInteger idx, BOOL *stop))block; // 遍历字典- (void)enumerateKeysAndObjectsUsingBlock:(void(^)(id key, id object, BOOL *stop))block;
// 遍历set
- (void)enumerateObjectsUsingBlock(void(^)(id object, BOOL *stop))block;- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id obj, NSUInteger idx, BOOL *stop))block;
`- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id key, id object, BOOL *stop))block;

基于块的遍历的好处

  1. 遍历时可以直接从块里获取更多信息。
  2. 遍历字典时无需额外编码,即可同时获取键与值,因而省去了根据给定键获取对应值这一步。这很可能比其他方式快很多。
  3. 能够修改快的签名,以免进行类型转换操作,从效果上讲,相当于把本来需要执行的类型转换操作交给块方法签名来做。
    例子:
    NSDictionry *aDictionry = /**/
    [aDictionry enuemrateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop)(
        // Do something with 'key' and 'obj'
    )];
    
  4. NSEnumberationOptions类型是一个enum,其各种值可用“按位或”(bitwise OR)连接,用以表明遍历方式。例如,开发者可以请求并发方式执行各轮迭代,也就是说,如果资源状况允许,那么执行每次迭代所用的块就可以并行执行了。通过NSEnumerationConcurrent选项即可开启此功能。如果使用此选项,那么底层会通过GCD来处理并发执行事宜。

要点

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

第 49条 对自定义其内存管理语义的collection使用无缝桥接

要点

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

第 50 条 构建缓存时选用NSCatch而非NSDictionary

NSCatch 与 NSDictionary相比,优势在哪儿?

  1. 当系统资源将要耗尽时,他可以自动删减缓存
  2. NSCatch还会先行删减“最久未使用”(lease recently used)对象
  3. NSCatch并不会“拷贝”键,而是会“保留”它。
  4. NSCatch是线程安全的

NSPurgeableData

NSPurgeableData是NSMutableData的子类,而且实现了NSDiscardableConent协议。如果某个对象所占的内存能够根据需要随时丢弃,那么就可以实现该协议所定义的借口。这就是说,当系统资源紧张时,可以把保存NSPurgeableData对象的那块内存释放掉。NSDiscardableContent协议里定义了名为isContentDiscarded的方法,可用来查询相关内存是否已释放。

NSCatch搭配NSPurgeableData使用

如果需要访问某个NSPurgeableData对象,可以调用beginContentAccess方法,告诉他现在还不应该丢弃自己所占据的内存。用完之后,调用endContentAccess方法,告诉他在必要时可以丢弃自己所占据的内存了。这些调用可以嵌套,所以说,他们就像递增和递减引用计数所用的方法那样。
如果将NSPurgeableData对象加入NSCatch,那么当该对象为系统所丢弃时,也会自动从缓存中移除。通过NSCache的evicsObjectsWithDiscardedContent属性,可以开启或关闭此功能。

`- (void)downLoadDataWithUrl:(NSUrl*)url {
     NSPurgeableData *cacheData = [_cache objectForKey:url]
     if (cacheData) {
        // Stop the data being purged
        [cacheData beginContentAccess];
        
        // Use the cached data
        [self useData:cacheData];
        
        // Mark that the data may be purged again
        [cacheData endContentAccess];
        
     }else {
        // Cache miss 
        EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithUrl:url]];
        [fetcher startWithCompletionHandler:^(NSData *data) { 
              NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
              [_cache setObject:purgeableData forKey:url cost:prugeableData.Length];
              
              // Don't need to beginContentAccess as it begins with access already marked
              
              // Use the retrieved data
              [self useData:data];
              
              // Mark that the data may be purged now
              [purgeableData endContentAccess];  
        }];
     }
}

注意,创建好了NSPurgeableData对象之后,起‘purge引用计数’会多一,所以无需再调用beginContentAccess了,然而其后必须调用endContentAccess,将多出来的这个‘1’抵消掉

要点

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

第 51 条 精简initialize 与 load的实现代码

+ (void)load

对于加入运行期系统的每个类(class)和 分类(category)来说,必定会调用此方法,而且仅调用一次。当包含类或分类的程序载入系统时,就会执行此方法,而这通常就是指应用程序启动的时候,若程序是为iOS平台设计的,则肯定会在此时执行。如果分类和其所属的类都定义了load方法,则先调用类里的,在调用分类里的。
load方法的问题在于,执行该方法时,运行期系统处于“脆弱状态”(fragile state)。在执行子类的load方法之前,必定会执行所有超类的load方法。如果代码还依赖了其他程序库,那么程序库里相关的load方法也必定会先执行。然而,根据某个给定的程序库,却无法判断出其中各个类的载入顺序。因此,在load方法中使用其他类时不安全的。比方说:

#import <Foundation/Foundation.h>
#import "EOCClass.h"

@interface EOCClassB : NSObject
@end
@implementation EOCClassB
+ (void)load {
   NSLog(@"Loading ClassB");
   EOCClassA *object = [[EOCClassA alloc] init];
}

此处使用NSLog没问题,而且相关字符也会照常纪记录,因为Foundation框架肯定在运行load方法之前就已经载入系统了。但是在EOCClassB的load方法里使用EOCClassA却不太安全,因为无法确定在执行EOCClassB的load方法之前,EOCClassA是不是已经加载好了。可以相见:EOCClassA这个类,也许会在其load方法中执行某些重要操作,只有执行完这些操作之后,该实力才能正常使用。
有个重要的事情需要注意,那就是load方法并不像普通的方法那样,他并不遵守那套继承规则。如果某个类本身没实现load方法,那么不管其各级超类是否实现此方法,系统都不回调用。此外,分类和其所属的类里,都可能出现load方法。此时两种实现代码都会调用,类的实现要比分类先执行。

为什么要精简load里的代码

  1. 因为整个应用程序在执行load方法时都会阻塞。如果load方法中包含繁杂的代码,那么应用程序在执行期间就会变得无响应。
  2. 不要在里面等待锁,也不要调用可能会加锁的方法。总之能不做的事精就别做。实际上,凡是想通过load在类加载之前执行某些任务的,基本都做的不太对。其真正用途仅在于调试程序,比如在分类类里编写此方法,用来判断该分类是否已经正确载入系统中。也许此方法一度很有用处,但现在完全可以说:时下编写Objective-C代码时,不需要它。

+(void)initialize

+(void)load 的调用时间在 +(void)initalize之前。
对于每个类来说,该方法会在程序首次用该类之前调用,且至调用一次。它是由运行期系统来调用的,绝不应该通过代码直接调用。

+ (void)initialize 与 +(void)load的区别

  1. +(void)load 执行在 +(void)initialize之前

  2. +(void)initialize是“惰性调用的”,也就是说,只有当程序用到了相关的类时,才会调用。因此,如果某个类一直都没有用,那么其initialize方法就一直不会运行。这也就等于说应用程序无需先把每个类的initialize都执行一遍。这与load不同,对于load来说,应用程序必须阻塞并等着所有类的load都执行完,才能继续。

  3. 运行期系统在执行该方法时,是处于正常状态的,因此,从运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能确保initialize方法一定会在“线程安全的环境”(thread-safe environment)中执行,这就是说,只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等着initialize执行完。

  4. initialize方法与其他消息一样,如果某个类为实现它,而其他超类实现了,那么就会运行超类的实现代码。

    # import <Foundation/Foundation.h>
    
    @interface EOCBaseClass : NSObject
    @end
    @implementation EOCBaseClass
    + (void)initialize {
       NSLog(@"%@ initialize",self);
    }
    @end
    
    @interface EOCSubClass : EOCBaseClass
    @ end 
    
    @implementation EOCSubClass
    @end
    

    即便EOCSubClass类没有实现initialize方法,他也会收到这条消息。由各级超类所实现的initialize也会先行调用。所以,首次使用EOCSubClass时,控制台会输出如下消息:

    EOCBaseClass initialize
    EOCSubClass initialize
    

+ (void)initialize注意

  1. 看过load与initialize方法的这些特征之后,又回到早前提过的那个主要问题上,也即是这两个方法的实现代码尽量精简。在里面设置一些状态,使本类能够正常运作就可以了,不要执行那种耗时太久或需要枷锁的任务。对于某个类来说,任何线程都有可能成为初次用到此方法的线程,并导致其初始化,如果这个线程碰巧是UI线程,那么初始化期间就会一直阻塞,导致该应用程序无响应。

  2. 开发者无法控制类的初始化时机。类在首次使用之前,肯定要初始化,但编写程序时不能令代码依赖特定的时间点,否则很危险。

  3. 如果某个类的实现代码很复杂,那么其中可能会直接或间接用到其他类。若那些类尚未初始化,则系统会迫使其初始化。然而本类初始化方法尚未运行完毕。其他类在运行其initialize方法时,有可能会依赖本类中的某些数据,而这些数据此时也许还未初始化好。例如

    #import <Foundation/Foundation.h>
    static id EOCClassAInternalData;
    @interface EOCClassB : NSObject
    @end
    
    @implementation EOCClassA
    + (void)initialize {
       if (self == [EOCClassA class]) {
           [EOCClassB doSomethingThatUsesInternalData];
           EOCClassAInternalData = [self setupInternalData];
       }
    }
    @end
    
    @implementation EOCClassB
    + (void)initialize {
       if (self == [EOCClassB class]){
          [EOCClassA doSomethingThatUsesInternalData];
          EOCClassBInternalData = [self setupInternalData];
       }
    }
    @end
    
    

    若是EOCClassA先初始化,那么EOCClassB随后也会初始化,他会在自己的初始化方法中调用EOCClassA的doSomethingThatUsesInternalData,而此时EOCClassA内部的数据还没准备好。

  4. 所以说initialize方法只应该用来设置内部数据。不应该在其中调用其他方法,即便是本类中自己的方法,也最好别调用。因为稍后可能还要给那些方法里添加更多功能,如果初始化过程中调用他们,那么还是有可能导致上面的问题。若某个全局状态无法在编译期初始化,则可以放在initialize里来做。如下:

     // EOCClass.h
     #import <Foundation/Foundation.h>
     
     @interface EOCClass : NSObject
     @end
     
     // EOCClass.m
     #import "EOCClass.h"
     static const int kInterval = 10;
     static NSMutableArray *kSomeObjects;
     
     @implementation EOCClass
     +(void)initialize {
        if (self == [EOCClass class]) {
            kSomeObjects = [[NSMutableArray alloc] init];
        }
     }
     @end
    
    

    整数可以在编译器定义,数组不可以,因为他是个Objective-C对象,所以创建实例之前必须先激活运行期系统。某些Objective-C对象也可以在编译器创建,比如NSStirng实例。然而下面这种对象慧玲编译器报错

    static NSMutableArray *kSomeojects = [[NSMutableArray alloc] init];
    

+(void)initialize的用处

  1. 初始化编译器不能创建的全局状态
  2. 除了初始化全局状态之外,如果还有其他事情要做,那么可以专门创建一个方法来执行这些操作,并要求该类的使用者必须在使用本类之前调用此方法。比如说,如果“单例类(singleton class)”在首次使用之前必须执行一些操作,那就可以采用这个办法。

要点

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

第 52 条 别忘了NSTimer会保留目标对象

计时器要和“运行循环”相关联,运行循环到时候会出发任务。创建NSTimer是,可以将其“预先安排”在当前的运行循环中,也可以创建好,然后由开发者自己来调度。无论哪种方式,只有把计时器放在运行循环里,他才能正常出发任务。
解决方式

#import <Foundation/Foundation.h>
@interface NSTimer (EOCBlockSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats
@end

@implementation NSTimer (EOCBlockSupport)
+ (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimerInterval)interval block:(void(^)())block repeats:(BOOL)repeats {
      return [self scheduletTimerWithInterval:interfal target:self selector:@selector(eoc_blockInvoke:) userInfo:([block copy]) repeats:repeats];
}

+ (void)eoc_blockInvoke:(NSTimer *)timer {
      void (^block)() = timer.userInfo
      if (block) {
          block();
      }
}

使用分类的过程
// self持用NSTimer,NSTimer持由block,block持有self,但是是若引用,所以构不成环
-(void)startPolling {
       __weak EOCClass *weakSelf = self;
       _pollTimer = [NSTimer eoc_scheduleTimerWithTimeInteraval:5.0 block:^{
       [weakSelf p_doPoll];
       } repeats:yes]
}

要点

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

相关文章

  • Effective Object-C 52:47-52

    七、系统框架 47.熟悉系统框架 许多系统框架可以直接使用比如Fundation、coreFoundation 很...

  • 系统框架--47:系统框架

    iOS基于UNIX系统,iOS的系统架构分为四层 可触摸层(Cocoa Touch layer)UIKit、Fou...

  • 系统框架

    第 7 章 系统框架第 47 条:熟悉系统框架CFNetworkCoreAudioAVFo...

  • iOS系统的底层通知框架库

    iOS系统的底层通知框架库 iOS系统的底层通知框架库

  • 框架系统

    IOC 控制反转容器控制程序对象之间的关系,而不是传统实现中,有程序代码之间控制,又名依赖注入。All 类的创建,...

  • 系统框架

    层级关系 物理层系列标准相互关系 上行共享信道UL-SCH编码流程框图:编码、复用、交织,数据来自MAC层 上行共...

  • 系统框架

    虽然不使用系统框架也能编写Objective-C代码,但几乎没人这么做。 即使是NSObject这个标准的根类,也...

  • 系统框架

    引言 APP主体功能采用object-c纯代码,个别模块由于旧版本原因采用xib 开发和故事面板(storyboa...

  • 系统框架

    四十七、熟悉系统框架 将一系列代码封装为动态库,并在其中放入描述其接口的头文件。这样做出来的东西就叫做框架。有时为...

  • 系统框架

    系统框架 单一应用框架 优点:当网站流量很小时,只需一个应用,将所有功能如下单支付等都部署在一起,以减少部署节点和...

网友评论

      本文标题:系统框架

      本文链接:https://www.haomeiwen.com/subject/kraniktx.html