• OC语言特性


    Category

    • 你用分类都做了哪些事情?
      • 声明私有方法
      • 分解体积庞大的类文件
      • 把Framework的私有方法公开
    • 特点
      • 在运行时决议,也就是在编译时并没有把Category中声明的内容添加到宿主类中,而是在运行的时候通过runtime将添加的方法添加到宿主类上面
      • 可以为系统添加分类
    • 分类中可以添加哪些内容?
      • 实例方法
      • 类方法
      • 协议
      • 属性
    • Category结构
    struct category_t {
        const char *name;	//分类名称
        classref_t cls;		//分类所属的宿主类
        struct method_list_t *instanceMethods;  //实例方法列表
        struct method_list_t *classMethods;  //类方法列表
        struct protocol_list_t *protocols;	//协议
        struct property_list_t *instanceProperties; //实例属性列表
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    
    
    • Category方法添加源码
    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        /*二维数组
         
         */
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;     //方法
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;    //宿主分类的总数
        bool fromBundle = NO;
        while (i--) {   //这里是倒叙遍历,最先访问最后编译的分类,所以多个分类添加的同名方法最后编译的最终生效
            //获取一个分类
            auto& entry = cats->list[i];
            //获取该分类的方法列表
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                //最后编译的分类最先添加到分类数组中
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
    
        //获取随著类当中的rw数据,其中包含宿主类的方法列表信息
        auto rw = cls->data();
    
        //主要针对分类中有关于内存管理想干方法情况下的一些特殊处理
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        
        /*
         rw代表类
         methods代表类的方法列表
         attachLists 方法的含义是 将含有没count个元素的mlists拼接到rw的methods上
         */
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
     
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
    
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    
    

    关联对象

    • 能否给分类添加“成员变量”?

      • 能通过关联对象的方式给分类添加成员变量
      //设定一个value值,通过key设定和value的映射关系,通过policy策略关联到对象上面
      objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                           id _Nullable value, objc_AssociationPolicy policy)
      
      //根据指定的key到object中获取和key相对应的关联值
      objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
      
      //根据指定对象移除它所有的关联对象
      objc_removeAssociatedObjects(id _Nonnull object)
      
      • 通过关联对象为分类添加的"成员变量"由AssociationsManager统一管理全部存储在AssociationsHashMap中,所有类的关联对象都存在同一个全局容器中
      void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
      // retain the new value (if any) outside the lock.
      ObjcAssociation old_association(0, nil);
      //根据policy的值对value进行copy或者retain操作
      id new_value = value ? acquireValue(value, policy) : nil;
      {
          //关联对象管理类,C++实现的一个类
          AssociationsManager manager;
          
          //获取其维护的一个HashMap
          AssociationsHashMap &associations(manager.associations());
          disguised_ptr_t disguised_object = DISGUISE(object);
          if (new_value) {
              // break any existing association.
              //根据对象指针查找对应的一个ObjectAssociationMap结构的map
              AssociationsHashMap::iterator i = associations.find(disguised_object);
              if (i != associations.end()) {
                  // secondary table exists
                  ObjectAssociationMap *refs = i->second;
                  ObjectAssociationMap::iterator j = refs->find(key);
                  if (j != refs->end()) {
                      old_association = j->second;
                      j->second = ObjcAssociation(policy, new_value);
                  } else {
                      (*refs)[key] = ObjcAssociation(policy, new_value);
                  }
              } else {
                  //没找到关联值的时候进行添加
                  // create the new association (first time).
                  ObjectAssociationMap *refs = new ObjectAssociationMap;
                  associations[disguised_object] = refs;
                  (*refs)[key] = ObjcAssociation(policy, new_value);
                  object->setHasAssociatedObjects();
              }
          } else {
              //当value传为空时则对该关联进行擦除操作
              // setting the association to nil breaks the association.
              AssociationsHashMap::iterator i = associations.find(disguised_object);
              if (i !=  associations.end()) {
                  ObjectAssociationMap *refs = i->second;
                  ObjectAssociationMap::iterator j = refs->find(key);
                  if (j != refs->end()) {
                      old_association = j->second;
                      refs->erase(j); //擦除操作
                  }
              }
          }
      }
      // release the old value (outside of the lock).
      if (old_association.hasValue()) ReleaseValue()(old_association);
      

    }
    ```

    扩展(Extension)

    • 作用
      • 声明私有属性,可以不被子类继承
      • 声明私有方法
      • 声明私有成员变量
    • 特点
      • 编译时决议
      • 只以声明的形式存在,多数情况下寄生于宿主类的.m中来进行声明方法的实现
      • 不能为系统类添加扩展

    代理(Delegate)

    • 准确的说是一种设计模式

    • iOS当中以@protocol形式体现

    • 传递方式一对一

    • 怎么声明必须实现方法和选择实现方法

      • 被标记为@required为必须实现的
      • 被标记为@optional为可选实现的
    • 容易出现循环引用,所以委托方需要通过weak关键字来弱化引用

    通知(NSNotification)

    • 通知是使用观察者模式来实现、用于跨层传递消息的机制
    • 通知和代理的区别
      • 代理是通过代理模式实现的,通知是通过观察者模式实现的
      • 代理是一对一,通知是一对多
    • 通知的实现流程
      • 发送者将发送的消息发送到通知中心
      • 通知中心广播给观察者
    • 通知的实现机制(代码未开源,猜测)
      • 底层应该维护一个Map表来存储注册的通知,同事每个通知下面设有观察者列表,当收到消息后则给观察者列表中的观察者发送消息

    KVO

    • Key-value observing的缩写
    • 是OC对观察者设计模式的又一实现
    • Apple使用了isa混写(isa-swizzling)来实现KVO(派生出一个名为NSKVONotifying_(ObjName)的子类并重写其setter方法)
    • KVO触发的问题
      • 通过外部直接修改属性可能(比如属性名设置isAge,通过key=age进行kvc设置)可以触发KVO监听方法(调动setter方法)
      • 通过KVC修改也可以触发KVO的回调
      • 通过直接修改成员变量无法触发KVO监听,需手动添加监听
      //直接为成员变量赋值
        [self willChangeValueForKey:@"value"];
        _value += 1;
        [self didChangeValueForKey:@"value"];
    

    KVC(键值编码技术)

    • Key-value coding的缩写
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    
    - (nullable id)valueForKey:(NSString *)key;
    
    • 通过键值编码技术可以修改对象同名成员变量的值,其中key是没有任何限制的,在外界可以通过key进行私有变量的修改,破坏了面向对象的思想方法(把对象行为属性结合为一个整体,隐藏内部实现,吧不想告诉别人的东西隐藏起来)
    • KVC调用流程
      • 若存set(key)的方法则优先调用该方法
      • 如果不存在set(key)方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,如果返回yes则按照会按照_key,_iskey,key,iskey的顺序搜索成员变量,如果返回NO则调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法抛出异常
    // 如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set<Key>:属性名时,会直接用setValue:forUndefinedKey:方法。如果返回YES则按照会按照_key,_iskey,key,iskey的顺序搜索成员变量
    
    + (BOOL)accessInstanceVariablesDirectly
    {
        return YES;
    }
    
    //setValue:forKey:通过_key,_iskey,key,iskey的顺序搜索成员变量查找仍未找到对应的key时调用该方法
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key
    {
        NSLog(@"%@", key);
    }
    
    //valueForKey:通过_key,_iskey,key,iskey的顺序搜索成员变量查找仍未找到对应的key时调用该方法
    - (id)valueForUndefinedKey:(NSString *)key
    {
        NSLog(@"%@", key);
        return [super valueForUndefinedKey:key];
    }
    

    属性关键字

    • 原子性
      • atomic修饰数组时对数组进行赋值和获取时是安全的,但是对数组进行增删是不在atomic责任之内的
      • nonmatic
    • 引用计数
      • retain/strong
      • assign/unsafe_unetained
      • weak
      • copy
      • assign
    • assign和weak区别
      • assign
        • assign可以修饰基本数据类型,如int,BOOL等;
        • 修饰对象类型时,不改变其引用计数
        • 会产生垂悬指针(assign修饰对象释放后指针会继续指向原地址,如果继续访问可能获取原对象造成异常)
      • weak
        • 不改变被修饰对象的引用计数
        • 所指对象在被释放后会自动置nil
      • assign既可以修饰对象又可以修饰基本数据类型,weak只能修饰对象
      • assign修饰对象释放后指针会继续指向原地址,而weak是置为nil的

    OC语言笔试题

    • MRC下如何重写retain修饰变量的setter方法?
    @property (nonatomic, retain) id obj;
    
    - (void)setName:(id)obj
    {
        if (_obj != obj) {
            [_obj release];
            _obj = [obj retain];
        }
    }
    
    
  • 相关阅读:
    java smtp 发送邮件
    Java 数据库连接配置
    kettle基础操作数据同步
    Java上传文件到服务器指定位置
    Java 操作word
    Java 导出Zip文件
    Java 遍历二叉树字符串
    一款基于vue.js 和node构建个人博客项目
    CSS选择器介绍和优先级
    CSS Position属性
  • 原文地址:https://www.cnblogs.com/GoodmorningMr/p/11536076.html
Copyright © 2020-2023  润新知