2016-02-25 11:39:03 qq_29892943 阅读数 5193

在ios中更改app的项目名字是一个麻烦事,不过有时候根据需要我们不得不去修改,正好这两天我也修改了一下我的app名字,就去研究了一下,今天拿来给大家分享。
下面,我就把一个app名字为‘西游记’的项目改为’水浒传‘把。

一:先看一下我们的项目,然后打开项目。

这里写图片描述

二:双击项目名字,将其改为‘水浒传’,点击确定。

这里写图片描述

接下来,会出现这样一个界面,这是xcode默认帮我们修改的一些东西,直接rename就行了,然后点ok完成修改。

这里写图片描述

有时候,我们更改完名字之后可能上面的运行部分变成了mac,一个齿轮。这时候我们关闭项目从新打开,看看可以不,如果不可以我们就点击下图我标识的地方创建一个新的sehemes,把老的更改为mac的那个删掉。

这里写图片描述

注意,这时候我们看这个项目的路径,相对路径已经变成了绝对路径,那么这时候在你的电脑上是可以的,如果你把这个项目发给别人的时候,或者把这个项目放到别的文件夹里面,这时候你再打开项目,项目就会变成红色。这时候,我们需要将项目删除,然后重新添加,这时候项目工程的相对路径才会正确。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

三:修改这两个文件,直接双击,将其中的‘西游记’改为‘水浒传’

这里写图片描述

这里写图片描述

相信大多数人在项目的搭建里面,都不会是虚目录,这些文件夹都是确确实实在我们的项目根目录的,那么我们接下来就改一下根目录的名字,点击项目名字 右键 show in finder

这里写图片描述

这里我们发现,我们虽然在项目中改了 但是在根目录里面并没有改掉,接下来我们把根目录的文件夹名字也改过来

这里写图片描述

打开项目我们会发现,出问题了,项目中这两个文件夹下的所有的文件都变成红色的了。

这里写图片描述

这时我们点一下水浒传这个文件夹 然后去看一下右边的属性栏,会发现它的路径还是指向原来的西游记路径

这里写图片描述

这时候我们点击红色圆圈里面的图标,给他从新选择正确的根目录路径,选择choose就可以了。

这里写图片描述

同样的道理,我们点击项目中的水浒传Tests,在右边属性栏选择新的目录路径。这时候我们会发现,所有的红色文件又都可以用了。

四:点击项目的搜索功能,将其改为Replace更换功能,输入西游记搜索然后更换为水浒传,这里面大部分是注释。

这里写图片描述

点击Replace All更换掉,更改完之后上面会出现绿色的对号。但是还有三处需要我们自己手动更改。如图根据箭头所指方向,我们手动将里面的‘西游记’改为‘水浒传’。

这里写图片描述

这里写图片描述

然后我们build一下,这时候会出现一个错误

这里写图片描述

然后我们再项目中去搜索这个‘西游记/Info.plist’,找到之后发现有两个,我们将其改为‘水浒传/Info.plist’就可以了。

这里写图片描述

五:本来到这里,项目已经可以运行,并且成功改好名字了,但是为了让我们的项目更加完美,在任何地方都找不到老项目的名字,我们还需最后一步。
关闭,xcode。打开项目的根路径,找到目录。如果我们的项目有.xcworkspace,那么我们会发现这个名字还没有改,双击现在将其改过来。
这里写图片描述

然后分别在如图两个工程下显示包内容

这里写图片描述

这里写图片描述

将这两个包里面的所有文件夹都点开一遍,凡是发现有‘西游记’的文件都将其改为‘水浒传’。改完之后,这回才叫一个干净彻底,项目从头到尾再也不会有旧名字了。

2018-07-13 15:34:42 chmod_R_755 阅读数 1666

iOS项目架构分为以下几步,这些都是总结的经验之谈


1.架构

  • 1.架构这个概念很抽象,但是用一句话来说叫看菜吃饭,有些人喜欢先看UI在架构,有些人喜欢先把该有的东西有了再看UI,其实都可以…..
  • 2.cocoapod 这个是必备的,现在第三方包基本用这个管理
  • 3.尽量找出项目中相似的东西:操作行为(eg:需求登录,需求权限,需求授权,分享等),视图行为(字体,字号,有规律控件);我们需要将这些相同的或者相似的东西尽量多找出来,写一些工具类或者写一个基类让自己的类继承
  • 4.不建议使用xib等可视图工具或插件开发iOS,不为什么,经验
  • 5.Xcode自带的svn/git 工具挺好,国内私有代码托管码云不错;githu是国际化路线私有收费不建议,而且天朝国情github速度性能托管代码没有码云好
  • 6.代码每一个固定的周期备份,在完成一个比较重要的模块的时候记得备份
  • 7.某个模块有独立的功能的时候,把模块独立出来,同样把模块的资源文件也独立出来;项目自带的Images.xcassets 里面文件分类最好对应相应的UIView
  • 8.开发证书先弄好,正式&开发 推送等证书先弄好,或许现在用不上用上的时候再搞听操蛋的 ; 各种秘钥先申请好 微信分享,微博分享,推送,统计,支付等等 一大推的秘钥我是建议在开发之前先申请好,别老是更新APP的名字或是icon 要改的地方很多 。。。
  • 9.建议iPhone和iPad分成两个项目来搞(小厂另说,若UI设计的很相似的话如微信可共用一套)

2.UI

  • 1.首先导入Masonry 不解释,肯定要用的
  • 2.确定下UI的布局形式:a)字号不变 b)字号变化,这里单独拿出来是因为ios的几个机型宽度高度存在着一定的差异,iPhoneX 和 iPhone6 同宽不同高,iPhone5和iPhone6及iPhone6Plus的关系应该是 iPhone5 ->@2x.png而 iPhone6Plus对应的是->@3x.png ,并不是iPhone6->*@2x.png ;但是平时UI妹子(也有汉子)在设计的时候会以iPhone6为准那么在640x1136的屏幕上怎么呈现需要先说明,别到最后BB的那就蛋疼了 ,然后是字号怎么变化的问题先说好
  • 3.android有android的UI风格,同样ios有ios的UI风格,SB的UI会说做成一样的;ios的状态栏,导航条,tabbar栏(tabbar里面的图片大小,文字大小其实都有一套标注的),toolbar栏的高度是固定,自定义UI另说
  • 4.iOS中可以使用第三方字体,注意版权的问题 (还是推荐使用苹果自己的字体)小厂没事情,大厂就瞎了 。。。。 不解释
  • 5.阿里icon里面有好多可以用的icon,同样注意版权问题
  • 6.icon需要1024x1024的(商店需要),有很多工具(有在线的)可以生产icon,所以你只需要一张1024的图就行,别自己ps处理浪费时间
  • 7.APPstore中 iPhone的图 可以截屏或自定定义;但是iPad的图可以截屏如果要自定义的话图片中必须有iPad(图片中必须画一个iPad,然后在iPad上画自定义UI,否则审核可能会不过的,不能直接拿着iPhone的图放大)

3.网络

  • 1.网络框架AFNetworking这个不解释,AFNetworking 是异步操作
  • 2.MVC这我我要说的是Model,应该这个会伴随整个项目,你选用的基础model(Manually,YYModel,FastEasyMapping,JsonModel,Mantle,MJExtension)可能会影响你写代码,我推荐使用YYModel,当然还有配套的工具 JSONModel 方便格式化成对象
  • 3.建议接口调用加密,加密方式我总计代码加密和证书加密;代码加密很简单,就是在传入参数的时候通过一定的排列规则组合成一个秘钥当参数传入,可以防止别人恶意刷接口(例如短信验证码),写一套加密规则;证书加密就比较麻烦,分单认证和双向认证,金融类的APP可以这么高,小厂APP这么搞时间成本高不建议(通常是服务端吃不消,小厂的服务端不像大厂,况且小厂哪有那么多屁事搞得跟真的似得)
  • 4.更新图片的时候图片需要重命名(这个有必要跟接口说下,有些接口就是SB),ios图片缓存是根据名称来缓存的
  • 5.AFNetworking其实自带缓存功能,网络请求方式有很多种、常用的是 POST和GET,其实还有DELETE,PUT,HEAD,PATCH,TRACE,OPTIONS(其实远不止这些但是get和post足够了)
  • 6.参数传递可以通过header和parameter两种方式

4.数据存储

  • 1.FMDB是个好的sqlite框架,这个不解释12K+的star
  • 2.最好有一点SQL基础,因为你可能需要自己写SQL语句
  • 3.数据库还有考虑以后增加字段等问题,数据库的类型不好把握数字(整形)用Integer其他用Text
  • 4.ios中支持可视化的数据库CoreData,而且CoreData有增删改查对应的方法或者事件,至于怎么选择看自己的习惯或者说看业务的要求
  • 5.CoreData也有很多优秀的开源项目

5.安全

  • 1.手机被越狱之后安全不是ios开发工程师考虑的
  • 2.网络请求这块建议https+证书认证,参数加盐加密(别只是SB的MD5),做到这两点可以抵挡99%非法操作(如果受剩下的1%入侵,我觉得你们厂至少在Hong Kong上市。。。都能在Hong Kong上市,那肯定不差钱请安全砖家了)
  • 3.如果说是图片类的公司,最好搞个神马水印上面,实在是搞不了水印的保存到本地可以把图片二进制文件打乱再保存,视频类文件也是一样的
  • 4.ios有加密APP的工具,不过收费的居多,但是app商店能抵挡一些恶意操作,总体感觉是加密的话成本较大不推荐
  • 5.授权安全:ios不像android,android偷偷地,后台偷偷地干些事情;iOS会有提醒,最常用的授权就是通信录、相机、相册、位置等
  • 6.注意私有API的使用,部分框架中包含了私有api,实时关注第三方API的更新情况

6.统计、bug管理

  • 1.统计无可厚非肯定是第三方统计,原因:技术成熟框架稳定;可以自己写统计,但是其一麻烦,其二维护难;统计现在好多公司在做,友盟,sharesdk,极光都在搞统计,友盟的统计是比较受认可的,友盟本来就是统计出生的,然后友盟的爹是阿里,不解释 ; 统计用友盟!!!
  • 2.统计这块会牵扯到一个问题,那就是版本:也就是项目必须有一个版本管理的模块,从项目准备做开始就得要设计这个模块
  • 3.统计为什么和bug管理扯上关系了,bug 管理也是和版本有关的 ,我们常用的远程bug工具是腾讯的bugly,友盟应该也有类似的工具,但是腾讯的这个还可以
  • 4.iOS不支持热修复 ,死了这条心
  • 5.版本号,其实这个没有神马共识,爱怎来怎么来,但是最好是出一套规定,版本号怎么更新
  • 6.统计,bug管理,版本管理最好在项目一开始就要干

7.分享

  • 1.分享为什么要单独拿出来了?首先分享有两大家, sharesdk 和 友盟,还有其他乱七八糟的不多介绍了,个人建议都用友盟的,因为你的统计用了友盟的了,你再用sharesdk分享的话,你的APP会大很多,减少APP大小从我做起
  • 2.截屏分享的功能苹果官方是禁止的,若查到无法上架的(凭运气)
  • 3.有支付和没有支付的分享导入的第三方包不一样
  • 4.几乎所有的第三方分享都支持用户自定义分享界面

8.推送

  • 1.推送几大家族: 极光,个推,百度云,友盟等 … 这么多推送其实都是为了照顾android,因为ios自己的推送本身很稳定,或者说很容易实现
  • 2.推送证书最好提前准备,正式版和测试版,名称、密码最好搞一样的,省的以后麻烦,当然你都导入了友盟统计和分享,推送也可以用友盟的,省事;但是android可能就没有那么走运了,中兴华为小米各个推送分分钟让人想自杀。。。。。
  • 3.推送证书有有效期的,推送不灵了,注意查看证书的有效期
  • 4.可以自定义推送,最好是能将推送与登录用户关联起来
  • 5.推送需要用户主动授权,不授权就瞎了

9.支付

  • 1.支付宝+微信
  • 2.支付宝开发难度低,微信开发难度超级难;主要是微信的文档太操蛋了,根本看不懂,跟水平无关
  • 3.客户端开发很简单,主要难点在服务端,但是有现成的集成框架;加密密钥全部在服务端,客户端要做的是请求接口获取支付参数就行,也无需神马高级的配置
  • 4.有集成好的第三方工具,ping++ 但是:收费
  • 5.支付回调,订单信息更新都需要服务端操作,禁止客户端改变订单状态

10.其他

  • 1.前期对需要越明确越好
  • 2.尽量控制APP的大小,当然是越小越好,能有颜色代码就不用图片;大图片资源建议压缩 tinypng不错
  • 3.有很多比较好的类扩展,比如md5,NSArray,NSDate等扩展肯定能用上,尽量平时多收集扩展
  • 4.频繁操作的函数独立出来,URL地址,通知,还有些宏定义最好都独立出来
  • 5.OC代码可以直接调用C和C++ 但是调用swift的时候需要一个中间文件
  • 6.测试调试尽量用真机
  • 7.ios支持png和jpg还有gif等格式

这里写图片描述

2018-04-06 12:52:58 u014084081 阅读数 648

iOS App Thinning

iOS App瘦身功能,不错的文章:

安装包的优化,参考iPhone安装包的优化

未使用的资源

资源中未使用的资源包括图片、音视频等

图片资源

图片方面的优化,包括图片压缩和去掉项目中未使用的图片资源

1.使用ImageOptim来做图片的压缩
2.使用LSUnusedResources来查找和删除未使用的图片资源

经我自己实践,发现ImageOptim虽然可以对图片(png和jpg类型)进行压缩,但压缩后并不能显著的减小ipa的体积(还是能减小,但是不多,我所经历的项目只减少了0.2M)。有文章说是使用ImageOptim,需要将Xcode中COMPRESS_PNG_FILES设置为NO。但我这样做后,效果还是和原来一样,感觉没什么效果

但删除掉项目中未使用的资源,包括图片、音视频、html/js文件,确实是可以减小ipa包的体积的

包内容

在ipa包中,比较大的内容,主要有2个:

  • Assets.car
  • app的可执行文件

Assets.car的文件,可参考Analysing Assets.car file in IOS
使用Asset Catalog Tinkerer来查看Assets.car的内容

二进制包

可使用MachOView来查看二进制包的组成,一般都包含2种架构:

  • armv7
  • arm64

可使用LinkMap来对文件进行分析,可以知道每个类在最终的可执行文件中占据的大小,就可以有针对性的进行优化

使用的时候需要将Write Link Map File选项设为yes

为使用的类和方法

推荐使用Appcode来做代码检查,code->inspect code代码检测分析功能删除无用类和无用方法

stackoverflow中有介绍使用Xcode来查找某个类的未使用的方法,参考:

有一个介绍是,使用assistant editor,选择callers,可发现某个方法是在哪些地方调用的

callers

参考

2019-08-23 17:31:25 HD6870 阅读数 81

1、本地化实现ios项目的APP桌面多国语言名称,以中、日、韩、英四国语言为例,默认项目是英文。建好项目后先点PROJECT这里面的项目名,再点下面Localizations选项里的Language,添加中日韩三国语言。弹出窗口全部勾选。

2、再新建一个Resorce里面的Strings文件,

3、保存的文件名一定要叫 InfoPlist.strings 。

4、然后点 InfoPlist.strings 文件,在右边的Locallzation下面点Localize,弹出窗口选择默认的English,再点Localize按钮。

5、这时候Locallzation下面多了个框,里面显示English已经选了,把剩下中日韩三国语言也都勾上。

6、勾完之后刚好对应左边的InfoPlist.strings项目的中日韩语言分类。

7、英语这里写 "CFBundleName" = "Tim Cook";   苹果CEO库克。

8、中文这里写 "CFBundleName" = "姚明";

日问这里写 "CFBundleName" = "はなざわ かな";  花泽香菜的日文名。

韩文这里写 "CFBundleName" = "최성국";  金馆长,崔成国的韩文名。

9、然后运行。手机语言是中文,就显示姚明。

10、把手机改成韩文系统,就显示 金馆长!

2016-06-08 16:33:09 offbye 阅读数 4933

近期开始研究Facebook f8app项目,目标是理解Facebook官方React Native f8app的整体技术架构,给公司目前几个的React Native项目开发提供官方经验借鉴,并对原生开发和React Native开发进行框架层面的融合。
本文分析f8app iOS代码的结构和技术实现,阅读本文的前提是对React Native和iOS开发有一定的了解。
f8app ios项目使用了CocosPod管理模块,现在RN的最新版本创建的项目默认已经不再使用CocosPods了,直接通过工程引用。f8app还是用了CocosPod,因此我们首先需要在ios目录下运行pod install,安装好依赖的项目,然后用Xcode打开F8v2.xcworkspace工作空间,注意不是打开F8v2.xcodeproj工程文件,我经常犯这个错误,实在不喜欢用CocosPods啊。

iOS f8app效果展示

先看下效果吧,在iOS模拟器上动效还是很好的。

f8appiOS

ios工程结构

首先还是先看下ios工程的结构:

.
├── Default-568h@2x.png
├── F8Scrolling.h
├── F8Scrolling.m
├── F8v2
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Base.lproj
│   │   └── LaunchScreen.xib
│   ├── Images.xcassets
│   │   ├── AppIcon.appiconset
│   │   │   ├── AppIcon@2x.png
│   │   │   ├── AppIcon@3x.png
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Info.plist
│   └── main.m
├── F8v2.xcodeproj
├── F8v2.xcworkspace
├── F8v2Tests
│   └── Info.plist
├── PodFile
├── Podfile.lock
├── Pods
│   ├── Bolts
│   │   ├── Bolts
│   │   │   ├── Common
│   │   │   └── iOS
│   │   ├── LICENSE
│   │   └── README.md
│   ├── FBSDKCoreKit
│   │   ├── FBSDKCoreKit
│   │   │   └── FBSDKCoreKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── FBSDKLoginKit
│   │   ├── FBSDKLoginKit
│   │   │   └── FBSDKLoginKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── FBSDKShareKit
│   │   ├── FBSDKShareKit
│   │   │   └── FBSDKShareKit
│   │   ├── LICENSE
│   │   └── README.mdown
│   ├── Headers
│   │   ├── Private
│   │   │   ├── Bolts
│   │   │   ├── CodePush
│   │   │   ├── FBSDKCoreKit
│   │   │   ├── FBSDKLoginKit
│   │   │   ├── FBSDKShareKit
│   │   │   ├── React
│   │   │   ├── react-native-fbsdkcore
│   │   │   ├── react-native-fbsdklogin
│   │   │   └── react-native-fbsdkshare
│   │   └── Public
│   │       ├── Bolts
│   │       ├── CodePush
│   │       ├── FBSDKCoreKit
│   │       ├── FBSDKLoginKit
│   │       ├── FBSDKShareKit
│   │       ├── React
│   │       ├── react-native-fbsdkcore
│   │       ├── react-native-fbsdklogin
│   │       └── react-native-fbsdkshare
│   ├── Local\ Podspecs
│   │   ├── CodePush.podspec.json
│   │   ├── React.podspec.json
│   │   ├── react-native-fbsdkcore.podspec.json
│   │   ├── react-native-fbsdklogin.podspec.json
│   │   └── react-native-fbsdkshare.podspec.json
│   ├── Manifest.lock
│   ├── Pods.xcodeproj
│   │   ├── project.pbxproj
│   │   ├── xcshareddata
│   │   │   └── xcschemes
│   └── Target\ Support\ Files
│       ├── Bolts
│       │   ├── Bolts-dummy.m
│       │   ├── Bolts-prefix.pch
│       │   └── Bolts.xcconfig
│       ├── CodePush
│       │   ├── CodePush-dummy.m
│       │   ├── CodePush-prefix.pch
│       │   └── CodePush.xcconfig
│       ├── FBSDKCoreKit
│       │   ├── FBSDKCoreKit-dummy.m
│       │   ├── FBSDKCoreKit-prefix.pch
│       │   └── FBSDKCoreKit.xcconfig
│       ├── FBSDKLoginKit
│       │   ├── FBSDKLoginKit-dummy.m
│       │   ├── FBSDKLoginKit-prefix.pch
│       │   └── FBSDKLoginKit.xcconfig
│       ├── FBSDKShareKit
│       │   ├── FBSDKShareKit-dummy.m
│       │   ├── FBSDKShareKit-prefix.pch
│       │   └── FBSDKShareKit.xcconfig
│       ├── Pods-F8v2
│       │   ├── Pods-F8v2-acknowledgements.markdown
│       │   ├── Pods-F8v2-acknowledgements.plist
│       │   ├── Pods-F8v2-dummy.m
│       │   ├── Pods-F8v2-frameworks.sh
│       │   ├── Pods-F8v2-resources.sh
│       │   ├── Pods-F8v2.debug.xcconfig
│       │   └── Pods-F8v2.release.xcconfig
│       ├── React
│       │   ├── React-dummy.m
│       │   ├── React-prefix.pch
│       │   └── React.xcconfig
│       ├── react-native-fbsdkcore
│       │   ├── react-native-fbsdkcore-dummy.m
│       │   ├── react-native-fbsdkcore-prefix.pch
│       │   └── react-native-fbsdkcore.xcconfig
│       ├── react-native-fbsdklogin
│       │   ├── react-native-fbsdklogin-dummy.m
│       │   ├── react-native-fbsdklogin-prefix.pch
│       │   └── react-native-fbsdklogin.xcconfig
│       └── react-native-fbsdkshare
│           ├── react-native-fbsdkshare-dummy.m
│           ├── react-native-fbsdkshare-prefix.pch
│           └── react-native-fbsdkshare.xcconfig
├── Settings.bundle
│   ├── About.plist
│   ├── Root.plist
│   └── en.lproj
│       └── Root.strings
├── Splash@2x.png
└── build

PodFile文件分析

PodFile是CocosPod的配置文件,是ruby语言写的,定义了用到的第三方模块,和一些处理过程。

source 'https://github.com/CocoaPods/Specs.git'

target 'F8v2' do
  pod 'React', :subspecs => [
    'Core',
    'RCTActionSheet',
    'RCTImage',
    'RCTNetwork',
    'RCTText',
    'RCTWebSocket',
    'RCTPushNotification',
    'RCTLinkingIOS',
    'RCTVibration',
  ], :path => '../node_modules/react-native'
  pod 'react-native-fbsdkcore', :path => '../node_modules/react-native-fbsdk/iOS/core'
  pod 'react-native-fbsdklogin', :path => '../node_modules/react-native-fbsdk/iOS/login'
  pod 'react-native-fbsdkshare', :path => '../node_modules/react-native-fbsdk/iOS/share'
  pod 'CodePush', :path => '../node_modules/react-native-code-push'
end

# Start the React Native JS packager server when running the project in Xcode.

start_packager = %q(
if nc -w 5 -z localhost 8081 ; then
  if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running" ; then
    echo "Port 8081 already in use, packager is either not running or not running correctly"
    exit 2
  fi
else
  open $SRCROOT/../../node_modules/react-native/packager/launchPackager.command || echo "Can't start packager automatically"
fi
)

post_install do |installer|
  target = installer.pods_project.targets.select{|t| 'React' == t.name}.first
  phase = target.new_shell_script_build_phase('Run Script')
  phase.shell_script = start_packager
end

通过CocosPod引入了React,react-native-fbsdkcore,react-native-fbsdklogin,react-native-fbsdkshare,CodePush这些模块,
最后会尝试启动React Native packager。

入口类AppDelegate.m代码分析

从项目的入口类AppDelegate.m看起,

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <CodePush/CodePush.h>

#import "AppDelegate.h"

#import "RCTRootView.h"
#import "RCTPushNotificationManager.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;

#ifdef DEBUG
  NSString *ip = [[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ip" ofType:@"txt"] encoding:NSUTF8StringEncoding error:nil] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n"]];

  if (!ip) {
    ip = @"127.0.0.1";
  }

  jsCodeLocation = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:8081/index.ios.bundle?platform=ios&dev=true", ip]];
#else
  jsCodeLocation = [CodePush bundleURL];
#endif
  NSLog(jsCodeLocation.absoluteString);

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"F8v2"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];

  NSArray *objects = [[NSBundle mainBundle] loadNibNamed:@"LaunchScreen" owner:self options:nil];
  UIImageView *loadingView = [[[objects objectAtIndex:0] subviews] objectAtIndex:0];
  loadingView = [[UIImageView alloc] initWithImage:[loadingView image]];
  loadingView.frame = [UIScreen mainScreen].bounds;

  rootView.loadingView = loadingView;

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [[UIViewController alloc] init];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [[UIApplication sharedApplication] setStatusBarHidden:NO];
  [self.window makeKeyAndVisible];

  return YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
  [FBSDKAppEvents activateApp];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  return [[FBSDKApplicationDelegate sharedInstance] application:application
                                                        openURL:url
                                              sourceApplication:sourceApplication
                                                     annotation:annotation];
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
  [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
}

@end

可以看到主要用了FBSDKCoreKit,CodePush热更新,RCTPushNotificationManager推送。
首先创建了RCTRootView *rootView,RCTRootView就是RN页面的容器,我们只要在iOS ViewController中添加RCTRootView就可以展示RN的页面了。
然后从LaunchScreen.xib中取出一个子View作为rootView的加载页面loadingView,设置view的frame,创建一个rootViewController,并把它的view设置成RCTRootView *rootView,然后把UIWindow的rootViewController设成刚才创建的rootViewController,这些代码还是很简单的。
didRegisterUserNotificationSettings,didRegisterForRemoteNotificationsWithDeviceToken,didReceiveRemoteNotification几个方法是对推送通知的处理,也比较简单。

F8Scrolling.m滚动UI组件代码分析

然后看下F8Scrolling.m,这个是RN的滚动UI组件,在f8app/js/common/ListContainer.js中用到了这个组件的js代码。我们看看代码里做了哪些事情:

#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>

#import "F8Scrolling.h"
#import "RCTUIManager.h"
#import "RCTScrollView.h"

@interface F8Scrolling () {
  NSMapTable *_pinnedViews;
  NSMapTable *_distances;
}

@end

@implementation F8Scrolling

@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

- (instancetype)init
{
  if (self = [super init]) {
    _pinnedViews = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableWeakMemory capacity:20];
    _distances = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:20];
  }
  return self;
}

- (dispatch_queue_t)methodQueue
{
  return dispatch_get_main_queue();
}

RCT_EXPORT_METHOD(pin:(nonnull NSNumber *)scrollViewReactTag
                  toView:(nonnull NSNumber *)pinnedViewReactTag
                  withDistance:(nonnull NSNumber *)distance)
{
  UIView *pinnedView = [self.bridge.uiManager viewForReactTag:pinnedViewReactTag];
  UIView *scrollView = [self.bridge.uiManager viewForReactTag:scrollViewReactTag];
  if ([scrollView isKindOfClass:[RCTScrollView class]]) {
    RCTScrollView *reactScrollView = (RCTScrollView *)scrollView;
    [_pinnedViews setObject:pinnedView forKey:reactScrollView.scrollView];
    [_distances setObject:distance forKey:reactScrollView.scrollView];
    [reactScrollView setNativeScrollDelegate:self];
    [self scrollViewDidScroll:reactScrollView.scrollView];
  }
}

RCT_EXPORT_METHOD(unpin:(nonnull NSNumber *)scrollViewReactTag)
{
  UIView *scrollView = [self.bridge.uiManager viewForReactTag:scrollViewReactTag];
  if ([scrollView isKindOfClass:[RCTScrollView class]]) {
    RCTScrollView *reactScrollView = (RCTScrollView *)scrollView;
    [_pinnedViews removeObjectForKey:reactScrollView.scrollView];
    [_distances removeObjectForKey:reactScrollView.scrollView];
    [reactScrollView setNativeScrollDelegate:nil];
  }
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  UIView *pinnedView = [_pinnedViews objectForKey:scrollView];
  if (!pinnedView) {
    return;
  }

  CGFloat distance = [[_distances objectForKey:scrollView] doubleValue];
  CGFloat y = MAX(0, distance - scrollView.contentOffset.y);
  pinnedView.transform = CGAffineTransformMakeTranslation(0, y);
}

@end

首先是类定义,oc里面是用@interface定义类的,这个和Java的interface很不一样,protocol对应Java的接口interface。
@interface F8Scrolling : NSObject <RCTBridgeModule, UIScrollViewDelegate>
init构造函数初始化了_pinnedViews和_distances2个变量,都是NSMapTable类型,NSMapTable(顾名思义)更适合于一般意义的映射。这取决于它是如何构造的,NSMapTable可以处理的“key-to-object”样式映射的NSDictionary,但它也可以处理“object-to-object”的映射 - 也被称为“associative array”或简称为“map”。_pinnedViews的key和value都是weak引用,_distances的key是weak引用,value是强引用。

@synthesize bridge=_bridge;意思是说,bridge 属性为 _bridge 实例变量合成访问器方法。
也就是说,bridge属性生成存取方法是setBridge,这个setWindow方法就是_bridge变量的存取方法,它操作的就是_bridge这个变量。通过这个看似是赋值的这样一个操作,我们可以在@synthesize 中定义与变量名不相同的getter和setter的命名,籍此来保护变量不会被不恰当的访问。

methodQueue返回了main_queue,规定这个组件运行在UI线程,因为它是UI组件啊
然后是几个方法的定义,RCT_EXPORT_METHOD宏提供导出方法到js的能力,可以用RCT_REMAP_METHOD重新定义在js中的函数名,还可以让js方法异步返回Promise,下面是它的定义

/**
 * Wrap the parameter line of your method implementation with this macro to
 * expose it to JS. By default the exposed method will match the first part of
 * the Objective-C method selector name (up to the first colon). Use
 * RCT_REMAP_METHOD to specify the JS name of the method.
 *
 * For example, in ModuleName.m:
 *
 * - (void)doSomething:(NSString *)aString withA:(NSInteger)a andB:(NSInteger)b
 * { ... }
 *
 * becomes
 *
 * RCT_EXPORT_METHOD(doSomething:(NSString *)aString
 *                   withA:(NSInteger)a
 *                   andB:(NSInteger)b)
 * { ... }
 *
 * and is exposed to JavaScript as `NativeModules.ModuleName.doSomething`.
 *
 * ## Promises
 *
 * Bridge modules can also define methods that are exported to JavaScript as
 * methods that return a Promise, and are compatible with JS async functions.
 *
 * Declare the last two parameters of your native method to be a resolver block
 * and a rejecter block. The resolver block must precede the rejecter block.
 *
 * For example:
 *
 * RCT_EXPORT_METHOD(doSomethingAsync:(NSString *)aString
 *                           resolver:(RCTPromiseResolveBlock)resolve
 *                           rejecter:(RCTPromiseRejectBlock)reject
 * { ... }
 *
 * Calling `NativeModules.ModuleName.doSomethingAsync(aString)` from
 * JavaScript will return a promise that is resolved or rejected when your
 * native method implementation calls the respective block.
 *
 */
#define RCT_EXPORT_METHOD(method) \
  RCT_REMAP_METHOD(, method)

方法pin从名字就可以知道,功能是固定view的。通过self.bridge.uiManager viewForReactTag方法获取到view。
方法scrollViewDidScroll最后定义了pinnedView的transform动画效果,pinnedView.transform = CGAffineTransformMakeTranslation(0, y);

F8v2目录下的代码文件基本上就介绍完了,Info.plist文件定义了项目的一些基本属性,包括CodePush key等的自定义属性。

总结

f8app iOS的代码量还是比较少的,本文主要分析了AppDelegate.m和 F8Scrolling UI组件的代码。项目还用了BVLinearGradient渐变UI组件,通过工程引用的,代码也比较简单。另外还通过CocosPod引入了React,react-native-fbsdkcore,react-native-fbsdklogin,react-native-fbsdkshare,CodePush这些模块,可以参考ios/PodFile。

[本文独立博客地址](http://www.offbye.com

iOS app签名机制

阅读数 1354

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