草庐IT

OC分类(category)和扩展(extension)

iOS小洁 2023-09-23 原文

首先,分别来说下分类(category)和扩展(extension)的用处。然后来解读一下分类的底层实现

分类常见的应用
1、可以将臃肿的类根据不同业务划分为多个模块,方便进行管理
2、对原有的类进行扩展,给类添加方法。特别是给系统自带的类方法

分类的注意点
1、分类里面可以定义@property属性,但是不会自动生成set和get方法,以及对应的成员变量
2、分类不能添加成员变量,但是可以通过关联对象的方式添加成员变量
3、分类中有和原有类同名的方法, 会优先调用分类中的方法
4、多个分类中同名方法,优先调用后面参与编译的分类里面的方法

扩展的应用
1、为一个类声明一些额外的方法和属性
2、常用在.m文件中添加一些私有属性。创建viewcontroller文件时会自动在.m文件内最上面实现扩展代码

扩展注意点
1、扩展只能声明方法、属性,具体实现需要在源文件中实现

分类的底层结构

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> 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);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

分类的加载过程
1、通过Runtime加载某个类的所有Category数据
2、把所有Category的方法、属性、协议数据,合并到一个大数组中。后面参与编译的Category数据,会在数组的前面
3、将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

源码调用顺序
1、_objc_init
2、map_images
3、map_images_nolock
4、_read_images
5、remethodizeClass
6、attachCategories
7、attachLists
8、realloc、memmove、 memcpy

通过分析就知道为什么分类不能添加成员变量,分类同名方法调用顺序

扩展
网上分类文章很多,但是关于扩展的文章较少,而且还都有错误,扩展的底层源码没法分析,下面就来验证一下网上关于扩展的说法

1、不能给系统类添加扩展,关于这个说法尝试一下几步
创建一个iOS工程,给NSObject添加一个扩展NSObject+Extension,编译没问题,运行没问题,所以可以添加扩展文件
在扩展文件中添加属性objName,编译没问题,运行没问题
在控制器中导入扩展的头文件,创建NSObject对象obj,给对象obj.objName赋值,编译没问题,运行报错unrecognized selector sent to instance 0x6000037b4630。找不到对应的方法
创建NSObject分类NSObject+Category,在分类.m文件中导入NSObject+Extension,在分类中使用属性objName。编译没问题,运行报错
在NSObject+Extension中添加方法声明,在分类中实现方法,在控制器中调用方法,编译没问题,运行没问题

总结如下,可以给系统类添加Extension,但是扩展中添加的方法和属性没有意义。
可以再扩展中添加方法声明,在分类中添加具体的方法实现。
在系统类扩展中声明的属性,不会自动生成set方法和get方法。可以再扩展中声明,在分类中添加关联对象。
由此可见,扩展就相当于对类.h的扩展,由于系统类.m文件拿不到,所以对系统类的扩展没有意义,还需要结合分类使用,反而要多导入一个头文件

2、类扩展中声明的方法没被实现,编译器会报警,但是分类中的方法没被实现编译器是不会有任何警告的
这个说法还是不对的
尝试了一下,结果是
扩展中声明的方法没有实现,会在原来的类里面警告
分类中明的方法没有实现,会在分类里面警告

3、非系统类扩展,只有把扩展头文件导入到原类的.m文件中会自动生成set和get方法。扩展声明的方法需要在分类或者元类的.m文件内实现

通过对扩展的分析,又拓展了一个扩展的应用场景,三方库对系统类添加的分类中如果有私有的方法,正常情况是没办法调用的,但是我们可以给这个系统类添加一个拓展,将私有方法在扩展中声明一下就可以调用了

有关OC分类(category)和扩展(extension)的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby-on-rails - 错误 : Error installing pg: ERROR: Failed to build gem native extension - 2

    我克隆了一个rails仓库,我现在正尝试捆绑安装背景:OSXElCapitanruby2.2.3p173(2015-08-18修订版51636)[x86_64-darwin15]rails-v在您的Gemfile中列出的或native可用的任何gem源中找不到gem'pg(>=0)ruby​​'。运行bundleinstall以安装缺少的gem。bundleinstallFetchinggemmetadatafromhttps://rubygems.org/............Fetchingversionmetadatafromhttps://rubygems.org/...Fe

  3. c - mkmf 在编译 C 扩展时忽略子文件夹中的文件 - 2

    我想这样组织C源代码:+/||___+ext||||___+native_extension||||___+lib||||||___(Sourcefilesarekeptinhere-maycontainsub-folders)||||___native_extension.c||___native_extension.h||___extconf.rb||___+lib||||___(Rubysourcecode)||___Rakefile我无法使此设置与mkmf一起正常工作。native_extension/lib中的文件(包含在native_extension.c中)将被完全忽略。

  4. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  5. ruby - 如何在 ruby​​ 中复制目录结构,不包括某些文件扩展名 - 2

    我想编写一个ruby​​脚本来递归复制目录结构,但排除某些文件类型。因此,给定以下目录结构:folder1folder2file1.txtfile2.txtfile3.csfile4.htmlfolder2folder3file4.dll我想复制这个结构,但不包含.txt和.cs文件。因此,生成的目录结构应如下所示:folder1folder2file4.htmlfolder2folder3file4.dll 最佳答案 您可以使用查找模块。这是一个代码片段:require"find"ignored_extensions=[".cs"

  6. ruby - 扩展类和实例 - 2

    这个问题有两个部分。在RubyProgrammingLanguage一书中,有一个使用模块扩展字符串对象和类的示例(第8.1.1节)。第一个问题。为什么如果您使用新方法扩展类,然后创建该类的对象/实例,则无法访问该方法?irb(main):001:0>moduleGreeter;defciao;"Ciao!";end;end=>nilirb(main):002:0>String.extend(Greeter)=>Stringirb(main):003:0>String.ciao=>"Ciao!"irb(main):004:0>x="foobar"=>"foobar"irb(main):

  7. ruby-on-rails - gem install rmagick -v 2.13.1 错误 Failed to build gem native extension on Mac OS 10.9.1 - 2

    我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/

  8. ruby - 动态扩展现有方法或覆盖 ruby​​ 中的发送方法 - 2

    假设我们有A、B、C类。Adefself.inherited(sub)#metaprogramminggoeshere#takeclassthathasjustinheritedclassA#andforfooclassesinjectprepare_foo()as#firstlineofmethodthenrunrestofthecodeenddefprepare_foo#=>prepare_foo()neededhere#somecodeendendBprepare_foo()neededhere#somecodeendend如您所见,我正在尝试将foo_prepare()调用注入

  9. ruby-on-rails - 如何扩展 Ruby Test::Unit 断言以包含 assert_false? - 2

    显然在Test::Unit中没有assert_false。您将如何通过扩展断言并添加文件config/initializers/assertions_helper.rb来添加它?这是最好的方法吗?我不想修改test/unit/assertions.rb。顺便说一句,我不认为这是多余的。我使用的是assert_equalfalse,something_to_evaluate。这种方法的问题是很容易意外使用assertfalse,something_to_evaluate。这将始终失败,不会引发错误或警告,并且会在测试中引入错误。 最佳答案

  10. ruby-on-rails - 无法构建 gem native 扩展 (mkmf (LoadError)) - Ubuntu 12.04 - 2

    这个问题在这里已经有了答案:Unabletoinstallgem-Failedtobuildgemnativeextension-cannotloadsuchfile--mkmf(LoadError)(17个答案)关闭9年前。嘿,我正在尝试在一台新的ubuntu机器上安装rails。我安装了ruby​​和rvm,但出现“无法构建gemnative扩展”错误。这是什么意思?$sudogeminstallrails-v3.2.9(没有sudo表示我没有权限)然后它会输出很多“获取”命令,最终会出现这个错误:Buildingnativeextensions.Thiscouldtakeawhi

随机推荐