欢迎来真孝善网,为您提供真孝善正能量书籍故事!

深入探索iOS开发技巧与最佳实践

时间:10-27 神话故事 提交错误

其实深入探索iOS开发技巧与最佳实践的问题并不复杂,但是又很多的朋友都不太了解,因此呢,今天小编就来为大家分享深入探索iOS开发技巧与最佳实践的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!

@实现ViewController

- (void)viewDidLoad {

[超级viewDidLoad];

UIView *view=[[UIView alloc]init];

view.backgroundColor=[UIColor greenColor];

view.frame=self.view.bounds;

view.center=self.view.center;

[self.view addSubview:view];

}

- (void)didReceiveMemoryWarning {

[超级didReceiveMemoryWarning];

//处理掉所有可以重新创建的资源。

}

@end运行后的效果不难想象,但是我想把效果图暂时放出来,如下

热更新前渲染.png

假设这是一个APP的背景颜色。上网后发现产品经理的设计图是红色背景的。感觉头晕,设计是绿色的。如果没有热更新,只能重新提交审核。如果做热更新,so Easy!如下

使用JSPatch 首先下载JSPatch的代码大家可以去github上面下载

在xcode中添加下载的JSPatch

在AppDelegate 中导入#import "JPEngine.h"

在下面的方法中添加如下代码

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

//应用程序启动后覆盖自定义点。

[JPEngine启动引擎];

NSString *sourcePath=[[NSBundle mainBundle] pathForResource:@"jspatch" ofType:@"js"];

NSString *script=[NSString stringWithContentsOfFile:sourcePath 编码:NSUTF8StringEncoding error:nil];

[JPEngine评估脚本:脚本];

[self.window makeKeyAndVisible];

返回是;

}有了上面的代码,我们就可以通过服务器给我们发送一个js文件来解决当前的问题了。文件中的内容是这样的

定义类("ViewController",{

viewDidLoad: 函数(){

self.super().viewDidLoad()

var view=require("UIView").alloc().init();

view.setBackgroundColor(require("UIColor").redColor());

view.setFrame(self.view().bounds());

view.setCenter(self.view().center());

self.view().addSubview(view);

},

});服务器端传出上面的js文件后,就可以将背景颜色改为红色了。

下面是JSPatch的基本介绍

基础原理:

JSPatch之所以能够通过JS调用和重写OC方法,最根本的原因是Objective-C是一种动态语言。 OC 上的所有方法调用/类生成都是在运行时通过Objective-C Runtime 执行的。我们可以通过类名/方法名的反射来获取对应的类和方法;还可以用新的实现替换某个类的方法,或者注册一个新的类并向该类添加方法;理论上,你可以在运行时通过类名/方法按名称调用任何OC方法,替换任何类的实现并添加任何新类。所以JSPatch的基本原理是:JS将字符串传递给OC,OC通过Runtime接口调用并替换OC的方法。

1. require

在使用Objective-C 类之前需要调用require("className") :

需要("UIView")

var view=UIView.alloc().init() 可以用逗号分隔,一次导入多个类:

需要("UIView,UIColor")

var view=UIView.alloc().init()

var red=UIColor.redColor() 或使用时直接调用require() :

require("UIView").alloc().init()

2. 调用OC方法

调用类方法

var redColor=UIColor.redColor();

调用实例方法

var view=UIView.alloc().init();

view.setNeedsLayout();

参数传递

和OC中一样传递参数:

var view=UIView.alloc().init();

var superView=UIView.alloc().init()

superView.addSubview(view)

Property

获取/修改一个Property相当于调用该Property的getter/setter方法。获取时记得加上():

view.setBackgroundColor(redColor);

var bgColor=view.backgroundColor();

方法名转换

多参数方法名称用_ 分隔:

var indexPath=require("NSIndexPath").indexPathForRow_inSection(0, 1);如果原来的OC方法名中含有下划线_,则在JS中使用双下划线__代替:

//Obj-C: [JPObject _privateMethod];

JPObject.__privateMethod()

3 defineClass

API

DefineClass(classDeclaration, [属性,] instanceMethods, classMethods)

@param classDeclaration: 字符串、类名/父类名和协议

@paramproperties: 新属性,字符串数组,可省略

@param instanceMethods: 要添加或重写的实例方法

@param classMethods: 要添加或覆盖的类方法

覆盖方法

1. 在defineClass 中定义OC。现有方法可以被覆盖。方法命名规则与调用规则相同。使用_ 分隔:

//超频

@实现JPTableViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

}

@结束//JS

DefineClass("JPTableViewController", {

tableView_didSelectRowAtIndexPath: 函数(tableView,indexPath){

.

},

})2.使用双下划线__表示原OC方法名中的下划线_:

//超频

@实现JPTableViewController

- (NSArray *) _dataSource {

}

@结束//JS

DefineClass("JPTableViewController", {

__dataSource: 函数() {

},

})3.方法名前添加ORIG 覆盖前调用原OC方法:

//超频

@实现JPTableViewController

- (void)viewDidLoad {

}

@结束//JS

DefineClass("JPTableViewController", {

viewDidLoad: 函数(){

self.ORIGviewDidLoad();

},

})

覆盖类方法

DefineClass() 第三个参数是要添加或重写的类方法。规则与上面重写的实例方法相同:

//超频

@实现JPTestObject

+ (void)共享实例

{

}

@结束//JS

DefineClass("JPTableViewController", {

//实例方法

}, {

//类方法

shareInstance: 函数() {

.

},

})

覆盖 Category 方法

重写Category 方法与重写普通方法相同:

@implementationUIView(自定义)

- (void)方法A {

}

+ (void)clsMethodB {

}

@结尾

定义类("UIView", {

方法A: 函数() {

}

}, {

clsMethodB: 函数(){

}

});

Super

使用self.super() 接口表示super 关键字并调用super 方法:

//JS

DefineClass("JPTableViewController", {

viewDidLoad: 函数(){

self.super().viewDidLoad();

}

})

Property

获取/修改OC定义的Property

通过调用getter/setter获取/修改OC中定义的Property:

//超频

@interface JPTableViewController

@property(非原子)NSArray *data;

@property (非原子) NSString *shareURL;

@property (非原子) NSString *shareTitle;

@结尾

@实现JPTableViewController

@结束//JS

DefineClass("JPTableViewController", {

viewDidLoad: 函数(){

var data=self.data(); //获取属性值

self.setData(data.toJS().push("JSPatch")); //设置属性值

var sel=自身;

self.bridge().registerHandler_handler("h5ToNativeShareDialog", block("NSDictionary *",function(data,responseCallback) {

sel.setShareURL(data.objectForKey("url"));

sel.setShareTitle(data.objectForKey("title"));

}));

})

动态新增 Property

您可以在defineClass() 的第二个参数中向类添加新属性。格式是字符串数组。使用时与OC属性接口一致:

DefineClass("JPTableViewController", ["数据", "totalCount"], {

init: 函数() {

self=self.super().init()

self.setData(["a", "b"]) //添加新属性(id数据)

self.setTotalCount(2)

返回自我

},

viewDidLoad: 函数(){

var data=self.data() //获取属性值

var TotalCount=self.totalCount()

},

})

私有成员变量

使用valueForKey() 和setValue_forKey() 获取/修改私有成员变量:

//超频

@implementation JPTableViewController {

NSArray *_data;

}

@结束//JS

DefineClass("JPTableViewController", {

viewDidLoad: 函数(){

var data=self.valueForKey("_data") //获取成员变量

self.setValue_forKey(["JSPatch"], "_data") //设置成员变量

},

})

添加新方法

可以随意给类添加OC未定义的方法,但所有参数类型都是id:

//超频

@实现JPTableViewController

- (void)viewDidLoad

{

NSString* data=[self dataAtIndex:@(1)];

NSLog(@"%@", 数据); //输出:补丁

}

@结束//JS

var data=["JS", "补丁"]

DefineClass("JPTableViewController", {

dataAtIndex: 函数(idx){

返回idx 数据长度?数据[idx]:""

}

}) 如果新添加的方法属于Protocol中的接口,则需要在defineClass的类声明参数中指定实现的Protocol。详情请参阅下文。

Protocol

您可以让类在定义期间实现某些协议接口。写入方法与OC :相同

DefineClass("JPViewController: UIViewController", {

这样做的效果是,当添加Protocol中定义的方法但类中没有实现的方法时,参数类型不再都是ids,而是自动转换为Protocol :中定义的类型

@协议UIAlertViewDelegate.

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;

.

@结尾

DefineClass("JPViewController: UIViewController", {

viewDidAppear: 函数(动画){

varalertView=require("UIAlertView")

.alloc()

.initWithTitle_message_delegate_cancelButtonTitle_otherButton 标题(

"警报",

self.dataSource().objectAtIndex(indexPath.row()),

自己,

"好的",

无效的

警报视图.show()}

AlertView_clickedButtonAtIndex: 函数(alertView,buttonIndex){

console.log("点击索引" + buttonIndex)

}

})

4. 特殊类型

Struct

JSPatch 原生支持四种结构类型CGRect/CGPoint/CGSize/NSRange,它们由JS 对象表示:

//对象-C

UIView *view=[[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];

[查看setCenter:CGPointMake(10,10)];

[视图sizeThatFits:CGSizeMake(100, 100)];CGFloat x=view.frame.origin.x;

NSRange范围=NSMakeRange(0, 1);//JS

var view=UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100})

view.setCenter({x: 10, y: 10})

view.sizeThatFits({width: 100, height:100})

var x=view.frame().x

var range={location: 0, length: 1} 对于其他结构类型支持,请参阅添加结构类型支持

Selector

在JS Selector: 中使用字符串表示

//对象C

[self PerformSelector:@selector(viewWillAppear:) withObject:@(YES)];//JS

self.performSelector_withObject("viewWillAppear:", 1)nil

JS中的null和undefined都代表OC的nil。如果要表示NSNull,请使用nsnull 代替。如果要表示NULL,请使用null 代替。

//对象C

@实现JPTestObject

+ (BOOL)testNull(NSNull *null) {

返回[null isKindOfClass:[NSNull 类]]

}

@结束//JS

require("JPTestObject").testNull(nsnull) //返回1

require("JPTestObject").testNull(null) //return 0 JS中判断是否为空,必须判断为false:

var url="";

var rawData=NSData.dataWithContentsOfURL(NSURL.URLWithString(url));

if (rawData !=null) {} //这个判断是错误的,应该这样判断:

if (!rawData){}

在JSPatch.js 源代码中,_formatOCToJS 方法将undefined、null 和isNil 转换为false。

5. NSArray / NSString / NSDictionary

NSArray/NSString/NSDictionary 不会自动转换为对应的JS 类型,请像普通NSObject 一样使用它们:

//对象C

@implementationJPObject

+ (NSArray *)数据

{

返回@[[NSMutableString stringWithString:@"JS"]]

}

+ (NSMutableDictionary *)dict

{

返回[[NSMutableDictionary alloc] init];

}

@结束//JS

要求("JPObject")

var ocStr=JPObject.data().objectAtIndex(0)

ocStr.appendString("补丁")

var dict=JPObject.dict()

dict.setObject_forKey(ocStr, "名称")

console.log(dict.objectForKey("name")) //output: JSPatch 如果要将NSArray/NSString/NSDictionary 转换为对应的JS 类型,请使用.toJS() 接口:

//JS

var data=require("JPObject").data().toJS()//数据实例Array===true

data.push("补丁")

var dict=JPObject.dict()

dict.setObject_forKey(data.join(""), "名称")

字典=dict.toJS()

console.log(dict["name"]) //输出: JSPatch

6. Block

block 传递

当给JS 函数作为块参数给OC 时,需要使用block(paramTypes, function) 接口来包装它:

//对象-C

@implementationJPObject

+ (void)request:(void(^)(NSString *content, BOOL success))回调

{

回调(@"我很满意",是);

}

@结束//JS

require("JPObject").request(block("NSString *, BOOL", function(ctn, succ) {

if (succ) log(ctn) //output: 我很满意

})) 这里,块中的参数类型由字符串表示。写下该块的每个参数的类型,以逗号分隔。 NSObject 对象如NSString *、NSArray 等可以用id 来表示,但是块对象必须用NSBlock 来表示。

OC返回给JS的block会自动转成JS函数,可以直接调用:

//对象-C

@实现JPObj

ecttypedef void (^JSBlock)(NSDictionary *dict); + (JSBlock)genBlock { NSString *ctn = @"JSPatch"; JSBlock block = ^(NSDictionary *dict) { NSLog(@"I"m %@, version: %@", ctn, dict[@"v"]) }; return block; } + (void)execBlock:(JSBlock)blk { } @end // JS var blk = require("JPObject").genBlock(); blk({v: "0.0.1"}); //output: I"m JSPatch, version: 0.0.1若要把这个从 OC 传过来的 block 再传回给 OC,同样需要再用 block() 包装,因为这里 blk 已经是一个普通的 JS function,跟我们上面定义的 JS function 没有区别: // JS var blk = require("JPObject").genBlock(); blk({v: "0.0.1"}); //output: I"m JSPatch, version: 0.0.1 require("JPObject").execBlock(block("id", blk));总结:JS 没有 block 类型的变量,OC 的 block 对象传到 JS 会变成 JS function,所有要从 JS 传 block 给 OC 都需要用 block() 接口包装。
block 里使用 self 变量
在 block 里无法使用 self 变量,需要在进入 block 之前使用临时变量保存它: defineClass("JPViewController", { viewDidLoad: function() { var slf = self; require("JPTestObject").callBlock(block(function(){ //`self` is not available here, use `slf` instead. slf.doSomething(); }); } }限制 从 JS 传 block 到 OC,有两个限制: A. block 参数个数最多支持6个。(若需要支持更多,可以修改源码) B. block 参数类型不能是 double / NSBlock / struct 类型。 另外不支持 JS 封装的 block 传到 OC 再传回 JS 去调用(原因见 issue #155): - (void)callBlock:(void(^)(NSString *str))block { } defineClass("JPTestObject", { run: function() { self.callBlock(block("NSString*", function(str) { console.log(str);

})); }, callBlock: function(blk) { //blk 这个 block 是上面的 run 函数里 JS 传到 OC 再传过来的,无法调用。 blk("test block"); } });

7. __weak / __strong

可以在 JS 通过 __weak() 声明一个 weak 变量,主要用于避免循环引用。 例如我们在 OC 里为了避免 block 导致的循环引用,经常这样写: - (void)test { __weak id weakSelf = self; [self setCompleteBlock:^(){ [weakSelf blabla]; }] }在 JS 对应的可以这样写: var weakSelf = __weak(self) self.setCompleteBlock(block(function(){ weakSelf.blabla(); }))若要在使用 weakSelf 时把它变成 strong 变量,可以用 __strong() 接口: var weakSelf = __weak(self) self.setCompleteBlock(block(function(){ var strongSelf = __strong(weakSelf) strongSelf.blabla(); }))

8. GCD

使用 dispatch_after() dispatch_async_main() dispatch_sync_main() dispatch_async_global_queue() 接口调用GCD方法: // Obj-C dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // do something }); dispatch_async(dispatch_get_main_queue(), ^{ // do something });// JS dispatch_after(1.0, function(){ // do something }) dispatch_async_main(function(){ // do something }) dispatch_sync_main(function(){ // do something }) dispatch_async_global_queue(function(){ // do something })

9. 传递 id* 参数

如果你需要传递 id* 参数,像 NSURLConnection 里的这个接口里的 NSError **: + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;这里传入的是一个指向 NSObject 对象的指针,在方法里可以修改这个指针指向的对象,调用后外部可以拿到新指向的对象,对于这样的参数,首先需要引入 JPMemory 扩展,然后按以下步骤进行传递和获取: 使用 malloc(sizeof(id)) 创建一个指针 把指针作为参数传给方法 方法调用完,使用 pval() 拿到指针新指向的对象 使用完后调用 releaseTmpObj() 释放这个对象 使用 free() 释放指针 举个例子: //OC - (void)testPointer:(NSError **)error { NSError *err = [[NSError alloc]initWithDomain:@"com.jspatch" code:42 userInfo:nil]; *error = err; }//JS //malloc() pval() free() is provided by JPMemory extensionrequire("JPEngine").addExtensions(["JPMemory"]) var pError = malloc(sizeof("id")) self.testPointer(pError) var error = pval(pError) if (!error) { console.log("success") } else { console.log(error) } releaseTmpObj(pError) free(pError)若反过来你想在 JS 替换上述 -testPointer: 方法,构建 NSError 对象赋给传进来的指针,可以这样写: defineClass("JPClassName", { testPointer: function(error){ var tmp = require("NSError").errorWithDomain_code_userInfo("test", 1, null); var newErrorPointer = getPointer(tmp) memcpy(error, newErrorPointer, sizeof("id")) } );

10. 常量、枚举、宏、全局变量

常量/枚举
Objective-C 里的常量/枚举不能直接在 JS 上使用,可以直接在 JS 上用具体值代替: //OC [btn addTarget:self action:@selector(handleBtn) forControlEvents:UIControlEventTouchUpInside]; //UIControlEventTouchUpInside的值是1<<6 btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);或者在 JS 上重新定义同名的全局变量: //js var UIControlEventTouchUpInside = 1<< 6; btn.addTarget_action_forControlEvents(self, "handleBtn", UIControlEventTouchUpInside);有些常量字符串,需要在 OC 用 NSLog 打出看看它的值是什么: //OC [[NSAttributedString alloc].initWithString:@"str" attributes:@{NSForegroundColorAttributeName: [UIColor redColor]];上面代码中 NSForegroundColorAttributeName 是一个静态字符串常量,源码里看不出它的值,可以先用 NSLog 打出它的值再直接写在 JS 上: //OC NSLog(@"%@", NSForegroundColorAttributeName) //output "NSColor" NSAttributedString.alloc().initWithString_attributes("无效啊", {"NSColor": UIColor.redColor()});宏
获取宏值
Objective-C 里的宏同样不能直接在 JS 上使用。若定义的宏是一个值,可以在 JS 定义同样的全局变量代替,若定义的宏是程序,可以在JS展开宏: #define TABBAR_HEIGHT 40 #define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.height [view setWidth:SCREEN_WIDTH height:TABBAR_HEIGHT];//JS view.setWidth_height(UIScreen.mainScreen().bounds().height, 40); 若宏的值是某些在底层才能获取到的值,例如 CGFLOAT_MIN,可以通过在某个类或实例方法里将它返回,或者用添加扩展的方式提供支持: @implementation JPMacroSupport + (void)main:(JSContext *)context { context[@"CGFLOAT_MIN"] = ^CGFloat() { return CGFLOAT_MIN; } } @end require("JPEngine").addExtensions(["JPMacroSupport"]) var floatMin = CGFLOAT_MIN();
修改宏值
JSPatch 不支持修改宏的值,若要修改,需要替换所有使用到这个宏的方法。例如: #define VIEW_HEIGHT 40 @implementation JPMethodDemo + (void)func { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, VIEW_HEIGHT)]; ... } @end//JS var VIEW_HEIGHT_NEW = 20;defineClass("JPMethodDemo", { func: function() { var view = UIView.alloc().initWithFrame({x:0, y:0, width:100, height:VIEW_HEIGHT_NEW}); ... } });
全局变量
在类里定义的 static 全局变量无法在 JS 上获取到,若要在 JS 拿到这个变量,需要在 OC 有类方法或实例方法把它返回: static NSString *name; @implementation JPTestObject + (NSString *)name { return name; } @end var name = JPTestObject.name() //拿到全局变量值

11. Swift

使用 defineClass() 覆盖 Swift 类时,类名应为 项目名.原类名,例如项目 demo 里用 Swift 定义了 ViewController 类,在 JS 覆盖这个类方法时要这样写: defineClass("demo.ViewController", {})对于调用已在 swift 定义好的类,也是一样: require("demo.ViewController")需要注意几点: 只支持调用继承自 NSObject 的 Swift 类 继承自 NSObject 的 Swift 类,其继承自父类的方法和属性可以在 JS 调用,其他自定义方法和属性同样需要加 dynamic 关键字才行。 若方法的参数/属性类型为 Swift 特有(如 Character / Tuple),则此方法和属性无法通过 JS 调用。 Swift 项目在 JSPatch 新增类与 OC 无异,可以正常使用。 详见这篇文章

12. 加载动态库

对于 iOS 内置的动态库,若原 APP 里没有加载,可以通过以下方式动态加载,以加载 SafariServices.framework 为例: var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework"); bundle.load();加载后就可以使用 SafariServices.framework 了。

13. 调试

可以使用 console.log() 打印一个对象,作用相当于 NSLog(),会直接在 XCode 控制台打出。 console.log() 支持任意参数,但不支持像 NSLog 这样 NSLog(@"num:%f", 1.0) 的拼接: var view = UIView.alloc().init(); var str = "test"; var num = 1; console.log(view, str, num)

用户评论

裸睡の鱼

想学iOS开发,感觉是个很好的方向!

    有16位网友表示赞同!

惯例

哪个平台iOS开发技术更吃香?

    有6位网友表示赞同!

淡抹烟熏妆丶

刚开始接触Swift语言,好难啊...

    有9位网友表示赞同!

嗯咯

有没有iOS开发的学习资源推荐?

    有16位网友表示赞同!

﹏櫻之舞﹏

学习iOS开发真需要耐心和毅力, 不止一次被卡住

    有11位网友表示赞同!

一样剩余

iOS App Store审核特别严格吧? 现在出App多容易吗?

    有8位网友表示赞同!

你与清晨阳光

做iOS开发感觉很有成就感呀! 自己开发出的APP能被人用。

    有10位网友表示赞同!

墨染殇雪

最近想尝试一下iOS游戏开发,应该怎么做入门?

    有5位网友表示赞同!

╭摇划花蜜的午后

学习iOS开发需要哪些必备的软件工具?

    有9位网友表示赞同!

神经兮兮°

想做一款简单的iPhone APP,不知道从哪里开始好?

    有14位网友表示赞同!

泡泡龙

iOS开发前景怎么样呢? 未来会越来越热门吗?

    有13位网友表示赞同!

忘故

学习iOS开发,有什么好就业方向么?

    有9位网友表示赞同!

孤者何惧

有哪些好的iOS开发者社区可以加入学习交流?

    有19位网友表示赞同!

孤独症

做iOS开发要掌握哪些关键技能?

    有15位网友表示赞同!

别伤我i

看到很多优秀的iOS Apps,都是自己动手做的吗?真厉害!

    有7位网友表示赞同!

败类

iOS开发和Android 开发相比有什么区别呢?

    有10位网友表示赞同!

孤败

学习iOS开发需要多少钱?

    有18位网友表示赞同!

爱你心口难开

对iOS开发特别感兴趣,以后一定要认真学习!

    有12位网友表示赞同!

【深入探索iOS开发技巧与最佳实践】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活