Skip to content

Instantly share code, notes, and snippets.

@sohocoke
Last active December 13, 2015 19:38
Show Gist options
  • Save sohocoke/4964060 to your computer and use it in GitHub Desktop.
Save sohocoke/4964060 to your computer and use it in GitHub Desktop.
framework 'Cocoa'
module DynamicKVCArrayMixin
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def attr_collection_accessor( property_name, synchronised = false )
property_name = property_name.to_s
# define basic accessors
attr_reader property_name
# self.def_method "set#{property_name.capitalize}" do |*args|
# backing_store.array = args
# end
self.add_indexed_accessors property_name
end
def add_indexed_accessors( property_name )
define_method :"countOf#{property_name.capitalize}" do
ivar = instance_variable_get "@#{property_name}"
ivar.count
end
define_method :"countOf#{property_name.capitalize}" do
ivar = instance_variable_get "@#{property_name}"
ivar.count
end
define_method :"objectIn#{property_name.capitalize}AtIndex:" do |index|
ivar = instance_variable_get "@#{property_name}"
ivar.objectAtIndex( index )
end
define_method :"get#{property_name.capitalize}:range:" do |objects, range|
ivar = instance_variable_get "@#{property_name}"
ivar.getObjects(objects, range:range)
end
define_method :"insertObject:in#{property_name.capitalize}AtIndex:" do |anObject, index|
ivar = instance_variable_get "@#{property_name}"
ivar.insertObject( anObject, atIndex:index)
end
define_method :"removeObjectFrom#{property_name.capitalize}AtIndex:" do |index|
ivar = instance_variable_get "@#{property_name}"
ivar.removeObjectAtIndex( index )
end
end
end
end
class NSArray
def to_index_set
index_set = NSMutableIndexSet.indexSet
self.each do |e|
index_set.addIndex(e)
end
NSIndexSet.alloc.initWithIndexSet(index_set)
end
end
require 'test/unit'
require 'DynamicKVCArrayMixin'
# ideal interface: an attr declared as a collection property (kvc array)
# ideal implementation: storage and kvo taken care of.
class ClassWithCollectionProperty
include DynamicKVCArrayMixin
attr_collection_accessor :my_array
def initialize
super
@my_array = []
end
end
class Test_DynamicKVCArrayMixin < Test::Unit::TestCase
def setup
super
@c = ClassWithCollectionProperty.new
puts "created #{@c}"
puts "ivar: #{@c.instance_variable_get '@my_array'}"
end
def test_attr_collection_accessor
verify_suite
end
def verify_suite
exercise_indexed_accessors
verify_kvo
end
def exercise_indexed_accessors
# check basic getter
assert_equal [], @c.valueForKey('my_array')
# insert using collection accessor
@c.insertObject(1, inMy_arrayAtIndex:0)
# check array content
assert_equal [1], @c.valueForKey('my_array')
end
def verify_kvo
observer = Object.new
class << observer
attr_accessor :called
def observeValueForKeyPath(keyPath, ofObject:object, change:change, context:context)
puts "#{object} sent a kvo notification"
self.called = true
end
end
@c.addObserver( observer, forKeyPath:'my_array',
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld, context:nil )
@c.insertObject( 2, inMy_arrayAtIndex: 0)
assert_equal [2, 1], @c.my_array
assert_equal observer.called, true
end
# directly accessing the object assigned as the collection property shouldn't result in the collection property being affected.
def test_direct_mutation
# objc
@c.my_array.addObject( 1, atIndex: 0)
assert_equal [1], @c.my_array
# ruby
@c.my_array << 2
assert_equal [1,2], @c.my_array
end
end
@sohocoke
Copy link
Author

when i use the mixin in an irb and inspect the dynamically defined methods:


o.methods(true,true).grep /array/
=> [:"replaceObjectInMy_arrayAtIndex:withObjec",
 :"replaceObjectInMy_arrayAtIndex:withObject:",
 :removeMy_arrayAtIndexes,
 :removeObjectFromMy_arrayAtIndex,
 :"insertMy_array:atIndexe",
 :"insertMy_array:atIndexes:",
 :"insertObject:inMy_arrayAtInde",
 :"insertObject:inMy_arrayAtIndex:",
 :"getMy_array:rang",
 :"getMy_array:range:",
 :objectInMy_arrayAtIndex,
 :countOfMy_array,
 :my_array]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment